Compare commits
66 Commits
a88bdc9639
...
1.2.2
| Author | SHA1 | Date | |
|---|---|---|---|
| ab1e847d1c | |||
| 62a03bfe6b | |||
| e77a8b3636 | |||
| 6bb7f03a3e | |||
| 8798a2f4fe | |||
| a6d2f6dca7 | |||
| b015d5880a | |||
| 6694711423 | |||
| 40c97ca000 | |||
| dd2437d4af | |||
| d026cfbb03 | |||
| aad7efd055 | |||
| 93c3bab263 | |||
| 5bd16b8fd4 | |||
| 3cc1459769 | |||
| 0655f2a39e | |||
| 57b1d46837 | |||
| 875759022a | |||
|
|
bb14ea34c3 | ||
|
|
149fb18564 | ||
| 10bc775e64 | |||
| 287b146621 | |||
|
|
371954373e | ||
|
|
ecef2c6cb0 | ||
|
|
6770533581 | ||
| 19a9c72c6d | |||
| fdb18d4ac5 | |||
|
|
303449e13c | ||
| b6d1b9398f | |||
| bb829cdd8e | |||
| 6b9bed5047 | |||
|
|
753c34bffa | ||
| 403178c832 | |||
| 33cd291c21 | |||
| 1c4f48bd65 | |||
| ec5e63898d | |||
| ea2a0e0e94 | |||
| 4f50d90b79 | |||
| bf1dc9da7a | |||
| 1a455a6025 | |||
| c9ac447484 | |||
| cb73f49962 | |||
| d9caf655fb | |||
| 1c3d2c8b48 | |||
| 9bd9cdaba4 | |||
| 72bfce7b08 | |||
| 6e48fcd5b9 | |||
| 2d98ff64ce | |||
| 6a14b87f44 | |||
| 63bcc2df5c | |||
| fd7ee1aa3b | |||
| 1073362007 | |||
| da5ad3b5cb | |||
| e77fbcaf5d | |||
| 111fe764f8 | |||
|
|
83857d7e87 | ||
| 83814311e5 | |||
| 55fdddeff8 | |||
| 1b21c87654 | |||
| df4823940d | |||
| 70532f6e2c | |||
| bd8fe50770 | |||
| e5d6ec9725 | |||
| c620c4f1ba | |||
| 6b48e80157 | |||
| fbbb8b8d65 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -3,3 +3,7 @@
|
|||||||
/build/
|
/build/
|
||||||
Build/
|
Build/
|
||||||
build/
|
build/
|
||||||
|
|
||||||
|
# VSCodeCounter
|
||||||
|
/.VSCodeCounter/
|
||||||
|
.VSCodeCounter/
|
||||||
10
.vscode/settings.json
vendored
Normal file
10
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"Lua.diagnostics.globals": [
|
||||||
|
"isEqualToAny",
|
||||||
|
"isEqualToAll",
|
||||||
|
"syscall",
|
||||||
|
"printf",
|
||||||
|
"printInline",
|
||||||
|
"toHex"
|
||||||
|
]
|
||||||
|
}
|
||||||
20
Makefile
Normal file
20
Makefile
Normal 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
|
||||||
51
README.md
51
README.md
@@ -1,5 +1,52 @@
|
|||||||
# HyperionOS
|
# HyperionOS
|
||||||
|
|
||||||
|
HyperionOS is a modular, hybrid kernel operating system written entirely in Lua. It features a custom task scheduler, virtual filesystem, syscall interface, and separates core functionality from user-space services.
|
||||||
|
|
||||||
---
|
---
|
||||||
# Desclaimer
|
|
||||||
To build
|
## 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 don’t 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.
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"Name": "Hyperion bash",
|
|
||||||
"Version": "1.0.0",
|
|
||||||
"Publishers": ["HyperionOS Dev Team"],
|
|
||||||
"Description": "basic bash shell",
|
|
||||||
"Dependencies": [
|
|
||||||
"https://git.astronand.dev/Hyperion/HyperionOS/archive/Hyperion-core-v1.0.0.pkg"
|
|
||||||
],
|
|
||||||
"Tar": "https://git.astronand.dev/Hyperion/HyperionOS/archive/Hyperion-bash-v1.0.0.tar"
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
syscall.TTY_clear()
|
|
||||||
--syscall.TTY_setTextColor(2)
|
|
||||||
syscall.TTY_setCursorPos(1, 1)
|
|
||||||
syscall.TTY_print("HyperionOS Bash Shell")
|
|
||||||
while true do
|
|
||||||
local event = syscall.IO_getEventAny()
|
|
||||||
if event then
|
|
||||||
syscall.TTY_print(table.serialize(event))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
print(".. *. ..")
|
|
||||||
print(" *= +@* +* ")
|
|
||||||
print(" .@#. -@@@= :#@. ")
|
|
||||||
print(" =@@+ *@@@# +@@= ")
|
|
||||||
print(" %@@%: *@@@# -%@@% ")
|
|
||||||
print(" :@@@@+ *@@@# .*@@@@: ")
|
|
||||||
print(" :*@@@%- *@@@# -@@@@*: ")
|
|
||||||
print(" =%@@#. *@@@# .#@@%= ")
|
|
||||||
print(" :=. :*@@= *@@@# =@@+: .=: ")
|
|
||||||
print(" %@#=..*# +@@@# #*..=#@# ")
|
|
||||||
print(" .@@@@+=# .%@%: #=+@@@@. ")
|
|
||||||
print(" .....=# -@= *+...:. ")
|
|
||||||
print(" -*%*-@= - =@-*%*- ")
|
|
||||||
print(" -@*. -@%. :%@- :*@- ")
|
|
||||||
print(" .#@#@* ")
|
|
||||||
print(" -#- ")
|
|
||||||
print(" ")
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
local fs = require("sys.fs")
|
|
||||||
local bashStr = fs.readAllText("/bin/bash")
|
|
||||||
local bashFun = load(bashStr)
|
|
||||||
syscall.HPV_spawn(bashFun, "bash")
|
|
||||||
33
Src/Hyperion-bash/bin/cat
Normal file
33
Src/Hyperion-bash/bin/cat
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
local args = {...}
|
||||||
|
local name = syscall.getTask(syscall.getpid()).name
|
||||||
|
local fs = require("sys.fs")
|
||||||
|
|
||||||
|
if not args[1] then
|
||||||
|
while true do
|
||||||
|
local content = syscall.read(0, 1024)
|
||||||
|
if not content or content == "" then break end
|
||||||
|
printInline(content)
|
||||||
|
end
|
||||||
|
print("")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, arg in ipairs(args) do
|
||||||
|
local filePath = arg
|
||||||
|
if filePath:sub(1,1) ~= "/" then
|
||||||
|
filePath = syscall.getcwd().."/"..filePath
|
||||||
|
end
|
||||||
|
|
||||||
|
if not fs.exists(filePath) then
|
||||||
|
print(name..": Cannot access '"..arg.."': No such file.")
|
||||||
|
else
|
||||||
|
local fd = syscall.open(filePath, "r")
|
||||||
|
while true do
|
||||||
|
local content = syscall.read(fd, 1024)
|
||||||
|
if not content or content == "" then break end
|
||||||
|
printInline(content)
|
||||||
|
end
|
||||||
|
syscall.close(fd)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
print("")
|
||||||
162
Src/Hyperion-bash/bin/chattr
Normal file
162
Src/Hyperion-bash/bin/chattr
Normal 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/Hyperion-bash/bin/chgrp
Normal file
117
Src/Hyperion-bash/bin/chgrp
Normal 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/Hyperion-bash/bin/chmod
Normal file
268
Src/Hyperion-bash/bin/chmod
Normal 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/Hyperion-bash/bin/chown
Normal file
150
Src/Hyperion-bash/bin/chown
Normal 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/Hyperion-bash/bin/chroot
Normal file
83
Src/Hyperion-bash/bin/chroot
Normal 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
|
||||||
1
Src/Hyperion-bash/bin/clear
Normal file
1
Src/Hyperion-bash/bin/clear
Normal file
@@ -0,0 +1 @@
|
|||||||
|
syscall.devctl(1,"clear")
|
||||||
2
Src/Hyperion-bash/bin/echo
Normal file
2
Src/Hyperion-bash/bin/echo
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
local args = {...}
|
||||||
|
print(table.concat(args, " "))
|
||||||
309
Src/Hyperion-bash/bin/help
Normal file
309
Src/Hyperion-bash/bin/help
Normal 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/Hyperion-bash/bin/hfetch
Normal file
100
Src/Hyperion-bash/bin/hfetch
Normal 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
|
||||||
1004
Src/Hyperion-bash/bin/hysh
Normal file
1004
Src/Hyperion-bash/bin/hysh
Normal file
File diff suppressed because it is too large
Load Diff
94
Src/Hyperion-bash/bin/hyshex
Normal file
94
Src/Hyperion-bash/bin/hyshex
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
--:Minify:--
|
||||||
|
syscall.open("/dev/tty/tty1","r")
|
||||||
|
syscall.open("/dev/tty/tty1","w")
|
||||||
|
syscall.open("/dev/null","r")
|
||||||
|
syscall.devctl(1,"clear")
|
||||||
|
syscall.devctl(1,"sfgc",1)
|
||||||
|
syscall.devctl(1,"spos",1,1)
|
||||||
|
print("HyperionOS hysh Shell")
|
||||||
|
local str=""
|
||||||
|
local stopInput=false
|
||||||
|
local proc=0
|
||||||
|
local fs=require("sys.fs")
|
||||||
|
local timeout=false
|
||||||
|
syscall.setEnviron("SHELL","simpleshell")
|
||||||
|
printInline("> ")
|
||||||
|
syscall.sigcatch(function(sig)
|
||||||
|
if sig==1 then
|
||||||
|
syscall.kill(proc)
|
||||||
|
print("Terminated")
|
||||||
|
printInline("> ")
|
||||||
|
stopInput=false
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
while true do
|
||||||
|
if not stopInput then
|
||||||
|
local input=syscall.read(0)
|
||||||
|
if input then
|
||||||
|
if input=="\b" then
|
||||||
|
if #str>0 then
|
||||||
|
str=str:sub(1,#str-1)
|
||||||
|
printInline("\b")
|
||||||
|
end
|
||||||
|
elseif input=="\n" then
|
||||||
|
print("")
|
||||||
|
stopInput=true
|
||||||
|
if str == "" then
|
||||||
|
printInline("> ")
|
||||||
|
stopInput=false
|
||||||
|
else
|
||||||
|
local path=nil
|
||||||
|
local split=string.split(str, " ")
|
||||||
|
if fs.exists("/bin/"..split[1]) then
|
||||||
|
path="/bin/"..split[1]
|
||||||
|
elseif fs.exists("/bin/"..split[1]..".lua") then
|
||||||
|
path="/bin/"..split[1]..".lua"
|
||||||
|
end
|
||||||
|
if not path then
|
||||||
|
print("Program not found")
|
||||||
|
printInline("> ")
|
||||||
|
stopInput=false
|
||||||
|
else
|
||||||
|
local text = fs.readAllText(path)
|
||||||
|
local program, err = load(text, path)
|
||||||
|
if not program then
|
||||||
|
print(err)
|
||||||
|
printInline("> ")
|
||||||
|
end
|
||||||
|
proc = syscall.spawn(function(...)
|
||||||
|
syscall.open("/dev/tty/tty1","r")
|
||||||
|
syscall.open("/dev/tty/tty1","w")
|
||||||
|
syscall.open("/dev/null","w")
|
||||||
|
program(...)
|
||||||
|
end, path, nil, {table.unpack(split, 2)})
|
||||||
|
end
|
||||||
|
str=""
|
||||||
|
end
|
||||||
|
else
|
||||||
|
str=str..input
|
||||||
|
printInline(input)
|
||||||
|
end
|
||||||
|
timeout=false
|
||||||
|
else
|
||||||
|
timeout=true
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local exited, code = syscall.collect(proc)
|
||||||
|
if exited then
|
||||||
|
if code then
|
||||||
|
print("\nTask exited with code:\n"..tostring(code))
|
||||||
|
end
|
||||||
|
printInline("> ")
|
||||||
|
stopInput=false
|
||||||
|
end
|
||||||
|
timeout=true
|
||||||
|
end
|
||||||
|
if timeout then
|
||||||
|
if stopInput then
|
||||||
|
sleep(.5)
|
||||||
|
else
|
||||||
|
sleep(.05)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
19
Src/Hyperion-bash/bin/id
Normal file
19
Src/Hyperion-bash/bin/id
Normal 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))
|
||||||
96
Src/Hyperion-bash/bin/ln
Normal file
96
Src/Hyperion-bash/bin/ln
Normal 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
|
||||||
169
Src/Hyperion-bash/bin/login
Normal file
169
Src/Hyperion-bash/bin/login
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
--:Minify:--
|
||||||
|
syscall.open("/dev/tty/tty1", "r") --stdin (fd 0)
|
||||||
|
syscall.open("/dev/tty/tty1", "w") --stdout (fd 1)
|
||||||
|
syscall.open("/dev/null", "w") --stderr (fd 2)
|
||||||
|
|
||||||
|
|
||||||
|
local 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 ok, err = syscall.login(username, 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/Hyperion-bash/bin/loimgcreate
Normal file
157
Src/Hyperion-bash/bin/loimgcreate
Normal 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("sys.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)
|
||||||
427
Src/Hyperion-bash/bin/looptest
Normal file
427
Src/Hyperion-bash/bin/looptest
Normal 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
|
||||||
129
Src/Hyperion-bash/bin/losetup
Normal file
129
Src/Hyperion-bash/bin/losetup
Normal 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/Hyperion-bash/bin/ls
Normal file
193
Src/Hyperion-bash/bin/ls
Normal 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("sys.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) 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", 4)
|
||||||
|
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", 4)
|
||||||
|
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/Hyperion-bash/bin/lsusers
Normal file
19
Src/Hyperion-bash/bin/lsusers
Normal 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
|
||||||
325
Src/Hyperion-bash/bin/lua
Normal file
325
Src/Hyperion-bash/bin/lua
Normal file
@@ -0,0 +1,325 @@
|
|||||||
|
--:Minify:--
|
||||||
|
local C_PROMPT = 7
|
||||||
|
local C_CONT = 13
|
||||||
|
local C_OUT = 5
|
||||||
|
local C_ERR = 2
|
||||||
|
local C_KEY = 3
|
||||||
|
local C_STR = 9
|
||||||
|
local C_NUM = 10
|
||||||
|
local C_BOOL = 8
|
||||||
|
local C_NIL = 12
|
||||||
|
local C_TABLE = 13
|
||||||
|
|
||||||
|
local function c(col) syscall.devctl(1, "sfgc", col) end
|
||||||
|
local function w(s) syscall.write(1, tostring(s)) end
|
||||||
|
|
||||||
|
local MAX_DEPTH = 6
|
||||||
|
local MAX_ENTRIES = 64
|
||||||
|
|
||||||
|
local function prettyVal(val, indent, seen)
|
||||||
|
indent = indent or 0
|
||||||
|
seen = seen or {}
|
||||||
|
local t = type(val)
|
||||||
|
|
||||||
|
if t == "nil" then
|
||||||
|
c(C_NIL); w("nil")
|
||||||
|
elseif t == "boolean" then
|
||||||
|
c(C_BOOL); w(tostring(val))
|
||||||
|
elseif t == "number" then
|
||||||
|
c(C_NUM)
|
||||||
|
if val ~= val then
|
||||||
|
w("nan")
|
||||||
|
elseif val == math.huge then
|
||||||
|
w("inf")
|
||||||
|
elseif val == -math.huge then
|
||||||
|
w("-inf")
|
||||||
|
elseif val == math.floor(val) and math.abs(val) < 1e15 then
|
||||||
|
w(tostring(math.floor(val)))
|
||||||
|
else
|
||||||
|
w(tostring(val))
|
||||||
|
end
|
||||||
|
elseif t == "string" then
|
||||||
|
c(C_STR)
|
||||||
|
local s = string.format("%q", val)
|
||||||
|
w(s)
|
||||||
|
elseif t == "function" then
|
||||||
|
c(C_TABLE); w(tostring(val))
|
||||||
|
elseif t == "table" then
|
||||||
|
if seen[val] then
|
||||||
|
c(C_TABLE); w("<circular " .. tostring(val) .. ">")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if indent >= MAX_DEPTH then
|
||||||
|
c(C_TABLE); w("<table " .. tostring(val) .. ">")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
seen[val] = true
|
||||||
|
|
||||||
|
local pad = string.rep(" ", indent)
|
||||||
|
local padIn = string.rep(" ", indent + 1)
|
||||||
|
|
||||||
|
local arrKeys = {}
|
||||||
|
local hashKeys = {}
|
||||||
|
local arrMax = #val
|
||||||
|
|
||||||
|
for k in pairs(val) do
|
||||||
|
if type(k) == "number" and k >= 1 and k <= arrMax and k == math.floor(k) then
|
||||||
|
arrKeys[#arrKeys+1] = k
|
||||||
|
else
|
||||||
|
hashKeys[#hashKeys+1] = k
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.sort(arrKeys)
|
||||||
|
|
||||||
|
table.sort(hashKeys, function(a, b)
|
||||||
|
local ta, tb = type(a), type(b)
|
||||||
|
if ta == tb then
|
||||||
|
if ta == "string" then return a < b end
|
||||||
|
if ta == "number" then return a < b end
|
||||||
|
return tostring(a) < tostring(b)
|
||||||
|
end
|
||||||
|
return ta < tb
|
||||||
|
end)
|
||||||
|
|
||||||
|
local total = #arrKeys + #hashKeys
|
||||||
|
if total == 0 then
|
||||||
|
c(C_TABLE); w("{}")
|
||||||
|
seen[val] = nil
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
c(C_TABLE); w("{\n")
|
||||||
|
|
||||||
|
local shown = 0
|
||||||
|
local function printEntry(k, v, isLast)
|
||||||
|
shown = shown + 1
|
||||||
|
if shown > MAX_ENTRIES then return true end
|
||||||
|
w(padIn)
|
||||||
|
if type(k) == "number" and arrKeys[k] then
|
||||||
|
else
|
||||||
|
if type(k) == "string" and k:match("^[a-zA-Z_][a-zA-Z0-9_]*$") then
|
||||||
|
c(C_KEY); w(k)
|
||||||
|
else
|
||||||
|
c(C_TABLE); w("[")
|
||||||
|
prettyVal(k, indent+1, seen)
|
||||||
|
c(C_TABLE); w("]")
|
||||||
|
end
|
||||||
|
c(C_TABLE); w(" = ")
|
||||||
|
end
|
||||||
|
prettyVal(v, indent+1, seen)
|
||||||
|
c(C_TABLE)
|
||||||
|
if not isLast then w(",") end
|
||||||
|
w("\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
for i, k in ipairs(arrKeys) do
|
||||||
|
w(padIn)
|
||||||
|
prettyVal(val[k], indent+1, seen)
|
||||||
|
c(C_TABLE)
|
||||||
|
if i < total then w(",") end
|
||||||
|
w("\n")
|
||||||
|
shown = shown + 1
|
||||||
|
if shown >= MAX_ENTRIES then
|
||||||
|
c(C_NIL); w(padIn .. "-- ..." .. (total - shown) .. " more entries\n")
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if shown < MAX_ENTRIES then
|
||||||
|
for i, k in ipairs(hashKeys) do
|
||||||
|
local isLast = (shown + 1 >= total)
|
||||||
|
w(padIn)
|
||||||
|
if type(k) == "string" and k:match("^[a-zA-Z_][a-zA-Z0-9_]*$") then
|
||||||
|
c(C_KEY); w(k)
|
||||||
|
else
|
||||||
|
c(C_TABLE); w("[")
|
||||||
|
prettyVal(k, indent+1, seen)
|
||||||
|
c(C_TABLE); w("]")
|
||||||
|
end
|
||||||
|
c(C_TABLE); w(" = ")
|
||||||
|
prettyVal(val[k], indent+1, seen)
|
||||||
|
c(C_TABLE)
|
||||||
|
shown = shown + 1
|
||||||
|
if shown < total then w(",") end
|
||||||
|
w("\n")
|
||||||
|
if shown >= MAX_ENTRIES then
|
||||||
|
local rem = total - shown
|
||||||
|
if rem > 0 then
|
||||||
|
c(C_NIL); w(padIn .. "-- ..." .. rem .. " more entries\n")
|
||||||
|
end
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
c(C_TABLE); w(pad .. "}")
|
||||||
|
seen[val] = nil
|
||||||
|
else
|
||||||
|
c(C_TABLE); w(tostring(val))
|
||||||
|
end
|
||||||
|
c(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function printResults(...)
|
||||||
|
local n = select("#", ...)
|
||||||
|
if n == 0 then return end
|
||||||
|
for i = 1, n do
|
||||||
|
if i > 1 then c(C_TABLE); w("\t") end
|
||||||
|
prettyVal(select(i, ...), 0, {})
|
||||||
|
end
|
||||||
|
w("\n")
|
||||||
|
c(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
local luaEnv = setmetatable({}, {__index = _ENV})
|
||||||
|
luaEnv._G = luaEnv
|
||||||
|
|
||||||
|
luaEnv.print = function(...)
|
||||||
|
local n = select("#", ...)
|
||||||
|
for i = 1, n do
|
||||||
|
if i > 1 then w("\t") end
|
||||||
|
prettyVal(select(i, ...), 0, {})
|
||||||
|
end
|
||||||
|
w("\n")
|
||||||
|
c(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
luaEnv.pp = function(val)
|
||||||
|
prettyVal(val, 0, {})
|
||||||
|
w("\n")
|
||||||
|
c(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
luaEnv.exit = setmetatable({}, {
|
||||||
|
__tostring = function() return "function: exit()" end,
|
||||||
|
__call = function() syscall.exit() end,
|
||||||
|
})
|
||||||
|
|
||||||
|
local function compile(code)
|
||||||
|
local exprFn = load("return " .. code, "@lua", "t", luaEnv)
|
||||||
|
if exprFn then return exprFn, true end
|
||||||
|
local stmtFn, err = load(code, "@lua", "t", luaEnv)
|
||||||
|
return stmtFn, false, err
|
||||||
|
end
|
||||||
|
|
||||||
|
local function isIncomplete(code)
|
||||||
|
local _, err = load(code, "@lua", "t", luaEnv)
|
||||||
|
return err and (err:find("<eof>") ~= nil or err:find("'end'") ~= nil
|
||||||
|
or err:find("'then'") ~= nil or err:find("'until'") ~= nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function cleanErr(msg)
|
||||||
|
return tostring(msg)
|
||||||
|
:gsub("^%[string .-%]:", "")
|
||||||
|
:gsub("^@lua:", "")
|
||||||
|
:gsub("stack traceback:.*", "")
|
||||||
|
:match("^%s*(.-)%s*$")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function runCode(code)
|
||||||
|
local fn, isExpr, err = compile(code)
|
||||||
|
if not fn then
|
||||||
|
c(C_ERR); w("[error] "); c(1); w(cleanErr(err) .. "\n")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local results = table.pack(xpcall(fn, debug.traceback))
|
||||||
|
local ok = table.remove(results, 1)
|
||||||
|
results.n = results.n - 1
|
||||||
|
|
||||||
|
if not ok then
|
||||||
|
c(C_ERR); w("[error] "); c(1); w(cleanErr(results[1]) .. "\n")
|
||||||
|
elseif isExpr and results.n > 0 then
|
||||||
|
c(C_OUT); w("= ")
|
||||||
|
printResults(table.unpack(results, 1, results.n))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getUserInput(prompt, history)
|
||||||
|
c(C_PROMPT); w(prompt); c(1)
|
||||||
|
local pos = syscall.devctl(1, "gpos")
|
||||||
|
local ox = tonumber(pos:sub(1, pos:find(";")-1))
|
||||||
|
local oy = tonumber(pos:sub(pos:find(";")+1))
|
||||||
|
|
||||||
|
local input = ""
|
||||||
|
local cursor = 1
|
||||||
|
local histIdx = 0
|
||||||
|
local blink = false
|
||||||
|
local dirty = true
|
||||||
|
|
||||||
|
local function redraw()
|
||||||
|
syscall.devctl(1, "spos", ox, oy)
|
||||||
|
w(input:sub(1, cursor-1))
|
||||||
|
if blink then syscall.devctl(1,"sfgc",16); syscall.devctl(1,"sbgc",1) end
|
||||||
|
w(cursor > #input and " " or input:sub(cursor, cursor))
|
||||||
|
syscall.devctl(1,"sfgc",1); syscall.devctl(1,"sbgc",16)
|
||||||
|
w(input:sub(cursor+1) .. " ")
|
||||||
|
dirty = false
|
||||||
|
end
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local key = syscall.read(0)
|
||||||
|
if key and key ~= "" then
|
||||||
|
if key == "\19" then
|
||||||
|
if cursor > 1 then cursor = cursor - 1; dirty = true end
|
||||||
|
elseif key == "\20" then
|
||||||
|
if cursor <= #input then cursor = cursor + 1; dirty = true end
|
||||||
|
elseif key == "\17" then
|
||||||
|
if history and histIdx < #history then
|
||||||
|
histIdx = histIdx + 1
|
||||||
|
input = history[#history - histIdx + 1]
|
||||||
|
cursor = #input + 1; dirty = true
|
||||||
|
end
|
||||||
|
elseif key == "\18" then
|
||||||
|
if histIdx > 1 then
|
||||||
|
histIdx = histIdx - 1
|
||||||
|
input = history[#history - histIdx + 1]
|
||||||
|
cursor = #input + 1; dirty = true
|
||||||
|
elseif histIdx == 1 then
|
||||||
|
histIdx = 0; input = ""; cursor = 1; dirty = true
|
||||||
|
end
|
||||||
|
elseif key == "\b" then
|
||||||
|
if cursor > 1 then
|
||||||
|
input = input:sub(1, cursor-2) .. input:sub(cursor)
|
||||||
|
cursor = cursor - 1; dirty = true
|
||||||
|
end
|
||||||
|
elseif key == "\n" then
|
||||||
|
syscall.devctl(1,"sfgc",1); syscall.devctl(1,"sbgc",16)
|
||||||
|
syscall.devctl(1,"spos",ox,oy)
|
||||||
|
w(input .. " \n")
|
||||||
|
return input
|
||||||
|
else
|
||||||
|
input = input:sub(1, cursor-1) .. key .. input:sub(cursor)
|
||||||
|
cursor = cursor + 1; dirty = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local nb = (math.floor(syscall.getUptime() / 500) % 2) == 0
|
||||||
|
if nb ~= blink then blink = nb; dirty = true end
|
||||||
|
if dirty then redraw() end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
syscall.devctl(1, "clear")
|
||||||
|
syscall.devctl(1, "spos", 1, 1)
|
||||||
|
c(C_PROMPT); w("HyperionOS " .. _VERSION .. "\n")
|
||||||
|
c(C_NIL)
|
||||||
|
w("Interactive Lua REPL. exit() to quit.\n\n")
|
||||||
|
c(1)
|
||||||
|
|
||||||
|
local history = {}
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local code = getUserInput("lua> ", history)
|
||||||
|
if code == "" then goto continue end
|
||||||
|
|
||||||
|
while isIncomplete(code) do
|
||||||
|
code = code .. "\n" .. getUserInput("... ", nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
if code ~= history[#history] then
|
||||||
|
history[#history+1] = code
|
||||||
|
end
|
||||||
|
|
||||||
|
runCode(code)
|
||||||
|
::continue::
|
||||||
|
end
|
||||||
50
Src/Hyperion-bash/bin/luaold
Normal file
50
Src/Hyperion-bash/bin/luaold
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
--:Minify:--
|
||||||
|
print("HyperionOS lua")
|
||||||
|
local str=""
|
||||||
|
local stopInput=false
|
||||||
|
local timeout=false
|
||||||
|
local luaEnv=setmetatable({},{__index=_ENV})
|
||||||
|
printInline("> ")
|
||||||
|
while true do
|
||||||
|
local input=syscall.read(0)
|
||||||
|
if input then
|
||||||
|
if input=="\b" then
|
||||||
|
if #str>0 then
|
||||||
|
str=str:sub(1,#str-1)
|
||||||
|
printInline("\b")
|
||||||
|
end
|
||||||
|
elseif input=="\n" then
|
||||||
|
print("")
|
||||||
|
stopInput=true
|
||||||
|
if str == "" then
|
||||||
|
printInline("> ")
|
||||||
|
stopInput=false
|
||||||
|
elseif str == "exit()" then
|
||||||
|
break
|
||||||
|
else
|
||||||
|
local func=load(str,"@Lua","t",luaEnv)
|
||||||
|
local ok,err = xpcall(func, debug.traceback)
|
||||||
|
if not ok then
|
||||||
|
print(err)
|
||||||
|
end
|
||||||
|
printInline("\n> ")
|
||||||
|
str=""
|
||||||
|
end
|
||||||
|
str=""
|
||||||
|
else
|
||||||
|
str=str..input
|
||||||
|
printInline(input)
|
||||||
|
end
|
||||||
|
timeout=false
|
||||||
|
else
|
||||||
|
timeout=true
|
||||||
|
end
|
||||||
|
|
||||||
|
if timeout then
|
||||||
|
if stopInput then
|
||||||
|
sleep(.5)
|
||||||
|
else
|
||||||
|
sleep(.05)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
423
Src/Hyperion-bash/bin/micro
Normal file
423
Src/Hyperion-bash/bin/micro
Normal file
@@ -0,0 +1,423 @@
|
|||||||
|
--: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 == "\27" 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 == "\17" then moveCursorUp(map); dirty=true
|
||||||
|
elseif key == "\18" then moveCursorDown(map); dirty=true
|
||||||
|
elseif key == "\19" then
|
||||||
|
if cx > 1 then cx=cx-1
|
||||||
|
elseif cy > 1 then cy=cy-1; cx=#lines[cy]+1 end
|
||||||
|
dirty=true
|
||||||
|
elseif key == "\20" then
|
||||||
|
if cx <= #lines[cy] then cx=cx+1
|
||||||
|
elseif cy < #lines then cy=cy+1; cx=1 end
|
||||||
|
dirty=true
|
||||||
|
elseif key == "\n" then newline()
|
||||||
|
elseif key == "\b" then delLeft()
|
||||||
|
elseif key == "\t" then for _=1,4 do insChar(" ") end
|
||||||
|
elseif b == 1 then cx=1; dirty=true
|
||||||
|
elseif b == 2 then
|
||||||
|
for _=1,ROWS do moveCursorUp(map) end; dirty=true
|
||||||
|
elseif b == 4 then delRight()
|
||||||
|
elseif b == 5 then cx=#lines[cy]+1; dirty=true
|
||||||
|
elseif b == 6 then
|
||||||
|
local p=prompt("Find: ",sPat); dirty=true
|
||||||
|
if p then sPat=p; sLine=0; findNext() end
|
||||||
|
elseif b == 7 then goToLine()
|
||||||
|
elseif b == 11 then cutLine()
|
||||||
|
elseif b == 12 then
|
||||||
|
for _=1,ROWS do moveCursorDown(map) end; dirty=true
|
||||||
|
elseif b == 14 then
|
||||||
|
if sPat=="" then
|
||||||
|
local p=prompt("Find: ",""); dirty=true
|
||||||
|
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
|
||||||
|
|
||||||
|
sleep(0.05)
|
||||||
|
end
|
||||||
|
|
||||||
|
tclear(); tfg(1); tbg(16); tpos(1,1)
|
||||||
|
print("edit: exited"..(fname and (" - "..fname) or ""))
|
||||||
23
Src/Hyperion-bash/bin/mkdir
Normal file
23
Src/Hyperion-bash/bin/mkdir
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
local args = {...}
|
||||||
|
local name = syscall.getTask(syscall.getpid()).name
|
||||||
|
if #args == 0 then
|
||||||
|
print(name..": Missing operand.")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local fs = require("sys.fs")
|
||||||
|
local newDir = args[1]
|
||||||
|
if newDir:sub(1, 1) ~= "/" then
|
||||||
|
newDir = syscall.getcwd().."/"..newDir
|
||||||
|
end
|
||||||
|
|
||||||
|
if newDir:sub(#newDir, #newDir) ~= "/" then
|
||||||
|
newDir = newDir.."/"
|
||||||
|
end
|
||||||
|
|
||||||
|
if fs.isDir(newDir) then
|
||||||
|
print(name..": Cannot create directory '"..args[1].."': Directory already exists.")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
fs.mkdir(newDir)
|
||||||
152
Src/Hyperion-bash/bin/mount
Normal file
152
Src/Hyperion-bash/bin/mount
Normal 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/Hyperion-bash/bin/passwd
Normal file
80
Src/Hyperion-bash/bin/passwd
Normal 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(targetName)
|
||||||
|
if not targetUid then
|
||||||
|
print("passwd: user '" .. targetName .. "' does not exist")
|
||||||
|
syscall.exit(1); return
|
||||||
|
end
|
||||||
|
-- Only root can change another user's password
|
||||||
|
if currentUid ~= 0 and targetUid ~= currentUid then
|
||||||
|
print("passwd: permission denied")
|
||||||
|
syscall.exit(1); return
|
||||||
|
end
|
||||||
|
else
|
||||||
|
targetUid = currentUid
|
||||||
|
targetName = syscall.getUsername(currentUid) or tostring(currentUid)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Non-root must verify their current password first
|
||||||
|
if currentUid ~= 0 then
|
||||||
|
printInline("Current password: ")
|
||||||
|
local cur = ""
|
||||||
|
while true do
|
||||||
|
local ch = syscall.read(0)
|
||||||
|
if not ch or ch == "" then
|
||||||
|
elseif ch == "\n" then syscall.write(1,"\n"); break
|
||||||
|
elseif ch == "\b" then
|
||||||
|
if #cur > 0 then cur=cur:sub(1,-2); syscall.write(1,"\b \b") end
|
||||||
|
else cur=cur..ch; syscall.write(1,"*") end
|
||||||
|
end
|
||||||
|
local ok, err = syscall.elevate(targetName, cur)
|
||||||
|
if not ok then
|
||||||
|
sleep(1)
|
||||||
|
print("passwd: authentication failure")
|
||||||
|
syscall.exit(1); return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
printInline("New password: ")
|
||||||
|
local pw1 = ""
|
||||||
|
while true do
|
||||||
|
local ch = syscall.read(0)
|
||||||
|
if not ch or ch == "" then
|
||||||
|
elseif ch == "\n" then syscall.write(1,"\n"); break
|
||||||
|
elseif ch == "\b" then
|
||||||
|
if #pw1 > 0 then pw1=pw1:sub(1,-2); syscall.write(1,"\b \b") end
|
||||||
|
else pw1=pw1..ch; syscall.write(1,"*") end
|
||||||
|
end
|
||||||
|
|
||||||
|
printInline("Confirm password: ")
|
||||||
|
local pw2 = ""
|
||||||
|
while true do
|
||||||
|
local ch = syscall.read(0)
|
||||||
|
if not ch or ch == "" then
|
||||||
|
elseif ch == "\n" then syscall.write(1,"\n"); break
|
||||||
|
elseif ch == "\b" then
|
||||||
|
if #pw2 > 0 then pw2=pw2:sub(1,-2); syscall.write(1,"\b \b") end
|
||||||
|
else pw2=pw2..ch; syscall.write(1,"*") end
|
||||||
|
end
|
||||||
|
|
||||||
|
if pw1 ~= pw2 then
|
||||||
|
print("passwd: passwords do not match")
|
||||||
|
syscall.exit(1); return
|
||||||
|
end
|
||||||
|
|
||||||
|
local ok, err = syscall.setpassword(targetUid, pw1)
|
||||||
|
if not ok then
|
||||||
|
print("passwd: " .. tostring(err))
|
||||||
|
syscall.exit(1); return
|
||||||
|
end
|
||||||
|
|
||||||
|
print("passwd: password updated for '" .. targetName .. "'")
|
||||||
4
Src/Hyperion-bash/bin/ps
Normal file
4
Src/Hyperion-bash/bin/ps
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
for i,v in ipairs(syscall.getTasks()) do
|
||||||
|
local task = syscall.getTask(v)
|
||||||
|
print(task.pid,task.username,task.name,task.status)
|
||||||
|
end
|
||||||
1
Src/Hyperion-bash/bin/pwd
Normal file
1
Src/Hyperion-bash/bin/pwd
Normal file
@@ -0,0 +1 @@
|
|||||||
|
print(syscall.getcwd())
|
||||||
82
Src/Hyperion-bash/bin/readlink
Normal file
82
Src/Hyperion-bash/bin/readlink
Normal 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
|
||||||
429
Src/Hyperion-bash/bin/sed
Normal file
429
Src/Hyperion-bash/bin/sed
Normal 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
|
||||||
151
Src/Hyperion-bash/bin/socktest
Normal file
151
Src/Hyperion-bash/bin/socktest
Normal 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
|
||||||
56
Src/Hyperion-bash/bin/su
Normal file
56
Src/Hyperion-bash/bin/su
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
--:Minify:--
|
||||||
|
local targetUser = ({ ... })[1] or "root"
|
||||||
|
local currentUid = syscall.getuid()
|
||||||
|
local targetUid = syscall.getuidbyname(targetUser)
|
||||||
|
|
||||||
|
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.elevate(targetUser, pw)
|
||||||
|
if not ok then
|
||||||
|
sleep(1)
|
||||||
|
print("su: Authentication failure")
|
||||||
|
syscall.exit(1)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
syscall.setuid(targetUid)
|
||||||
|
|
||||||
|
local pwent = syscall.getpasswd(targetUid)
|
||||||
|
local shell = (pwent and pwent.shell) or "/bin/hysh"
|
||||||
|
local homedir = (pwent and pwent.homedir) or "/"
|
||||||
|
|
||||||
|
local 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", targetUser)
|
||||||
|
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/Hyperion-bash/bin/sudo
Normal file
110
Src/Hyperion-bash/bin/sudo
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
--:Minify:--
|
||||||
|
local fs = require("sys.fs")
|
||||||
|
|
||||||
|
local cmdArgs = {...}
|
||||||
|
local targetUser = "root"
|
||||||
|
local i = 1
|
||||||
|
|
||||||
|
if cmdArgs[i] == "-u" then
|
||||||
|
i = i + 1
|
||||||
|
local uarg = cmdArgs[i] or "root"
|
||||||
|
local numUid = tonumber(uarg)
|
||||||
|
if numUid then
|
||||||
|
local pwent = syscall.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.elevate("root", 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
|
||||||
5
Src/Hyperion-bash/bin/sysdump
Normal file
5
Src/Hyperion-bash/bin/sysdump
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
local syscalls=syscall.sysdump()
|
||||||
|
for i=1, #syscalls do
|
||||||
|
print(syscalls[i])
|
||||||
|
end
|
||||||
|
print("Total # of syscalls: "..tostring(#syscalls))
|
||||||
111
Src/Hyperion-bash/bin/umount
Normal file
111
Src/Hyperion-bash/bin/umount
Normal 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/Hyperion-bash/bin/useradd
Normal file
67
Src/Hyperion-bash/bin/useradd
Normal 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/Hyperion-bash/bin/userdel
Normal file
49
Src/Hyperion-bash/bin/userdel
Normal 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("sys.fs")
|
||||||
|
local ok2, err2 = pcall(function()
|
||||||
|
local function rmdir(path)
|
||||||
|
for _, f in ipairs(fs.list(path) or {}) do
|
||||||
|
local full = path .. "/" .. f
|
||||||
|
if fs.isDir(full) then rmdir(full)
|
||||||
|
else syscall.remove(full) end
|
||||||
|
end
|
||||||
|
syscall.remove(path)
|
||||||
|
end
|
||||||
|
if fs.exists(pwent.homedir) then rmdir(pwent.homedir) end
|
||||||
|
end)
|
||||||
|
if not ok2 then
|
||||||
|
print("userdel: warning: could not remove home: " .. tostring(err2))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
print("userdel: deleted user '" .. username .. "'")
|
||||||
49
Src/Hyperion-bash/bin/usermod
Normal file
49
Src/Hyperion-bash/bin/usermod
Normal 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 .. "'")
|
||||||
1
Src/Hyperion-bash/bin/whoami
Normal file
1
Src/Hyperion-bash/bin/whoami
Normal file
@@ -0,0 +1 @@
|
|||||||
|
print((syscall.getUsername() or "Unknown"))
|
||||||
8
Src/Hyperion-bash/bin/yes
Normal file
8
Src/Hyperion-bash/bin/yes
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
local args = {...}
|
||||||
|
while true do
|
||||||
|
if #args == 0 then
|
||||||
|
print("y")
|
||||||
|
else
|
||||||
|
print(table.concat(args, " "))
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"Name": "Hyperion core",
|
|
||||||
"Version": "1.0.0",
|
|
||||||
"Publishers": ["HyperionOS Dev Team"],
|
|
||||||
"Description": "Core system files and librarys for HyperionOS",
|
|
||||||
"Dependencies": [
|
|
||||||
"https://git.astronand.dev/Hyperion/HyperionOS/archive/Hyperion-kernel-v1.0.0.pkg"
|
|
||||||
],
|
|
||||||
"Tar": "https://git.astronand.dev/Hyperion/HyperionOS/archive/Hyperion-core-v1.0.0.tar"
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
local sys = {}
|
|
||||||
local fs = require("sys.fs")
|
|
||||||
|
|
||||||
function sys.spawn(func, name, envars, args)
|
|
||||||
return coroutine.yield(0x10, func, name, envars, args)
|
|
||||||
end
|
|
||||||
|
|
||||||
function sys.spawnFromFile(path, name, envars, args)
|
|
||||||
local data = fs.readAllText(path)
|
|
||||||
if not data then
|
|
||||||
error("File not found: "..path,2)
|
|
||||||
end
|
|
||||||
local func, err = load(data, "@"..path)
|
|
||||||
if not func then
|
|
||||||
error("Error loading file "..path..": "..tostring(err),2)
|
|
||||||
end
|
|
||||||
return coroutine.yield(0x10, func, name, envars, args)
|
|
||||||
end
|
|
||||||
|
|
||||||
function sys.spawnAndWait(func, name, envars, args)
|
|
||||||
local task = coroutine.yield(0x10, func, name, envars, args)
|
|
||||||
local oldsignal = sys.getSignalHandler(17)
|
|
||||||
local exit = false
|
|
||||||
sys.setSignalHandler(17, function()
|
|
||||||
local tasks = sys.getChildrenTasks(task)
|
|
||||||
if not tasks[task] then
|
|
||||||
exit = true
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
while not exit do
|
|
||||||
coroutine.yield()
|
|
||||||
end
|
|
||||||
sys.setSignalHandler(17, oldsignal)
|
|
||||||
return task
|
|
||||||
end
|
|
||||||
|
|
||||||
function sys.spawnFromFileAndWait(path, name, envars, args)
|
|
||||||
local data = fs.readAllText(path)
|
|
||||||
if not data then
|
|
||||||
error("File not found: "..path,2)
|
|
||||||
end
|
|
||||||
local func, err = load(data, "@"..path)
|
|
||||||
if not func then
|
|
||||||
error("Error loading file "..path..": "..tostring(err),2)
|
|
||||||
end
|
|
||||||
return sys.spawnAndWait(func, name, envars, args)
|
|
||||||
end
|
|
||||||
|
|
||||||
function sys.exit(code)
|
|
||||||
return coroutine.yield(0x11, code)
|
|
||||||
end
|
|
||||||
|
|
||||||
function sys.getTaskInfo(task)
|
|
||||||
return coroutine.yield(0x12, task)
|
|
||||||
end
|
|
||||||
|
|
||||||
return sys
|
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
-- bit32.lua
|
--:Minify:--
|
||||||
-- Full pure-Lua implementation of Lua 5.2 bit32 library
|
|
||||||
-- NO Lua 5.3 operators used
|
|
||||||
|
|
||||||
local bit32 = {}
|
local bit32 = {}
|
||||||
|
|
||||||
@@ -136,16 +134,4 @@ function bit32.replace(x, v, field, width)
|
|||||||
return bit32.bor(x, bit32.lshift(v, field))
|
return bit32.bor(x, bit32.lshift(v, field))
|
||||||
end
|
end
|
||||||
|
|
||||||
-- ===== Test =====
|
|
||||||
|
|
||||||
function bit32.test(x, ...)
|
|
||||||
local args = {...}
|
|
||||||
for i = 1, #args do
|
|
||||||
if bit32.band(x, args[i]) ~= 0 then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
return bit32
|
return bit32
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
--:Minify:--
|
||||||
-- blake2s.lua
|
-- blake2s.lua
|
||||||
-- Pure Lua 5.2, 32-bit only, supports keyed hashing
|
-- Pure Lua 5.2, 32-bit only, supports keyed hashing
|
||||||
|
|
||||||
6
Src/Hyperion-core/lib/io
Normal file
6
Src/Hyperion-core/lib/io
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
local io = {}
|
||||||
|
local fs = require("sys.fs")
|
||||||
|
|
||||||
|
function io.open(path, mode)
|
||||||
|
return fs.open(path, mode)
|
||||||
|
end
|
||||||
@@ -1,32 +1,32 @@
|
|||||||
|
--:Minify:--
|
||||||
local fs={}
|
local fs={}
|
||||||
|
|
||||||
-- "VFS_open" : open
|
-- "open" : open
|
||||||
-- "VFS_read" : read
|
-- "read" : read
|
||||||
-- "VFS_write" : write
|
-- "write" : write
|
||||||
-- "VFS_close" : close
|
-- "close" : close
|
||||||
|
|
||||||
function fs.open(path, mode)
|
function fs.open(path, mode)
|
||||||
local fd=syscall.VFS_open(path,mode)
|
local fd=syscall.open(path,mode)
|
||||||
local ret={
|
local ret={
|
||||||
close=function()
|
close=function()
|
||||||
-- close file
|
-- close file
|
||||||
return syscall.VFS_close(fd)
|
return syscall.close(fd)
|
||||||
end,
|
end,
|
||||||
flush=function()
|
flush=function()
|
||||||
-- close and reopen file to flush buffers
|
-- close and reopen file to flush buffers
|
||||||
syscall.VFS_close(fd)
|
syscall.fsync(fd)
|
||||||
fd=syscall.VFS_open(path,mode)
|
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
if mode=="r" then
|
if mode=="r" then
|
||||||
ret.read=function(count)
|
ret.read=function(count)
|
||||||
local data = syscall.VFS_read(fd,count)
|
local data = syscall.read(fd,count)
|
||||||
return data
|
return data
|
||||||
end
|
end
|
||||||
ret.readAll=function(chunkSize)
|
ret.readAll=function(chunkSize)
|
||||||
local chunks={} -- to store read chunks
|
local chunks={} -- to store read chunks
|
||||||
while true do
|
while true do
|
||||||
local chunk=syscall.VFS_read(fd,chunkSize or 65536)
|
local chunk=syscall.read(fd,chunkSize or 65536)
|
||||||
if chunk==nil or #chunk==0 then break end
|
if chunk==nil or #chunk==0 then break end
|
||||||
table.insert(chunks,chunk)
|
table.insert(chunks,chunk)
|
||||||
end
|
end
|
||||||
@@ -59,7 +59,7 @@ function fs.open(path, mode)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Read the next chunk
|
-- Read the next chunk
|
||||||
local chunk = syscall.VFS_read(fd, chunk_size)
|
local chunk = syscall.read(fd, chunk_size)
|
||||||
if not chunk or chunk == "" then
|
if not chunk or chunk == "" then
|
||||||
eof = true
|
eof = true
|
||||||
else
|
else
|
||||||
@@ -70,12 +70,12 @@ function fs.open(path, mode)
|
|||||||
elseif mode=="w" then
|
elseif mode=="w" then
|
||||||
ret.write=function(data)
|
ret.write=function(data)
|
||||||
-- write data to file
|
-- write data to file
|
||||||
return syscall.VFS_write(fd,data)
|
return syscall.write(fd,data)
|
||||||
end
|
end
|
||||||
elseif mode=="a" then
|
elseif mode=="a" then
|
||||||
ret.write=function(data)
|
ret.write=function(data)
|
||||||
-- append data to file
|
-- append data to file
|
||||||
return syscall.VFS_write(fd,data)
|
return syscall.write(fd,data)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
error("Invalid mode '"..mode.."'",2)
|
error("Invalid mode '"..mode.."'",2)
|
||||||
@@ -105,35 +105,39 @@ function fs.appendAllText(path, data)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function fs.mkdir(path)
|
function fs.mkdir(path)
|
||||||
return syscall.VFS_mkdir(path)
|
return syscall.mkdir(path)
|
||||||
end
|
end
|
||||||
|
|
||||||
function fs.remove(path)
|
function fs.remove(path)
|
||||||
return syscall.VFS_remove(path)
|
return syscall.remove(path)
|
||||||
end
|
end
|
||||||
|
|
||||||
function fs.list(path)
|
function fs.list(path)
|
||||||
return syscall.VFS_list(path)
|
return syscall.listdir(path)
|
||||||
end
|
end
|
||||||
|
|
||||||
function fs.type(path)
|
function fs.type(path)
|
||||||
return syscall.VFS_type(path)
|
return syscall.type(path)
|
||||||
end
|
end
|
||||||
|
|
||||||
function fs.attributes(path)
|
function fs.stat(path)
|
||||||
return syscall.VFS_attributes(path)
|
return syscall.stat(path)
|
||||||
end
|
end
|
||||||
|
|
||||||
function fs.exists(path)
|
function fs.exists(path)
|
||||||
return syscall.VFS_exists(path)
|
return syscall.exists(path)
|
||||||
end
|
end
|
||||||
|
|
||||||
function fs.getcwd()
|
function fs.getcwd()
|
||||||
return syscall.VFS_getcwd()
|
return syscall.getcwd()
|
||||||
end
|
end
|
||||||
|
|
||||||
function fs.setcwd(path)
|
function fs.chdir(path)
|
||||||
return syscall.VFS_setcwd(path)
|
return syscall.chdir(path)
|
||||||
|
end
|
||||||
|
|
||||||
|
function fs.isDir(path)
|
||||||
|
return syscall.type(path) == "directory"
|
||||||
end
|
end
|
||||||
|
|
||||||
return fs
|
return fs
|
||||||
6
Src/Hyperion-core/lib/sys/hpv
Normal file
6
Src/Hyperion-core/lib/sys/hpv
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
local sys = {}
|
||||||
|
local fs = require("sys.fs")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return sys
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
|
--:Minify:--
|
||||||
local kernel=...
|
local kernel=...
|
||||||
local fs=require("sys.fs")
|
local fs=require("sys.fs")
|
||||||
syscall.TTY_bind("tty0")
|
|
||||||
|
|
||||||
for i,v in pairs(kernel.processes) do
|
for i,v in pairs(kernel.processes) do
|
||||||
kernel.log("Spawning kernel task "..i)
|
kernel.log("Spawning kernel task "..i)
|
||||||
syscall.HPV_spawn(function()
|
syscall.spawn(function()
|
||||||
local status, err = pcall(v)
|
local status, err = pcall(v)
|
||||||
if not status then
|
if not status then
|
||||||
kernel.log("Error executing kernel task '" .. i .. "': " .. err, "ERROR")
|
kernel.log("Error executing kernel task '" .. i .. "': " .. err, "ERROR")
|
||||||
@@ -14,6 +14,9 @@ for i,v in pairs(kernel.processes) do
|
|||||||
end, i)
|
end, i)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if not fs.exists("/bin/startup") then
|
||||||
|
fs.mkdir("/bin/startup")
|
||||||
|
end
|
||||||
local files = fs.list("/bin/startup")
|
local files = fs.list("/bin/startup")
|
||||||
if not files then error("Failed to list /bin/startup") end
|
if not files then error("Failed to list /bin/startup") end
|
||||||
for i,v in ipairs(files) do
|
for i,v in ipairs(files) do
|
||||||
@@ -24,7 +27,8 @@ for i,v in ipairs(files) do
|
|||||||
if not startupFunc then
|
if not startupFunc then
|
||||||
kernel.log("Error loading startup script '" .. filepath .. "': " .. err, "ERROR")
|
kernel.log("Error loading startup script '" .. filepath .. "': " .. err, "ERROR")
|
||||||
else
|
else
|
||||||
syscall.HPV_spawn(function()
|
syscall.spawn(function()
|
||||||
|
syscall.setuid(1)
|
||||||
local status, err = pcall(startupFunc)
|
local status, err = pcall(startupFunc)
|
||||||
if not status then
|
if not status then
|
||||||
kernel.log("Error executing startup script '" .. filepath .. "': " .. err, "ERROR")
|
kernel.log("Error executing startup script '" .. filepath .. "': " .. err, "ERROR")
|
||||||
@@ -37,6 +41,6 @@ for i,v in ipairs(files) do
|
|||||||
end
|
end
|
||||||
|
|
||||||
while true do
|
while true do
|
||||||
|
sleep(1)
|
||||||
kernel.saveLog()
|
kernel.saveLog()
|
||||||
sleep(1000)
|
|
||||||
end
|
end
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"Name": "Hyperion CCT Firmware",
|
|
||||||
"Version": "1.0.0",
|
|
||||||
"Publishers": ["HyperionOS Dev Team"],
|
|
||||||
"Description": "The ComputerCraftTweaked firmware package for HyperionOS.",
|
|
||||||
"Dependencies": [
|
|
||||||
"https://git.astronand.dev/Hyperion/HyperionOS/archive/Hyperion-kernel-v1.0.0.pkg"
|
|
||||||
],
|
|
||||||
"Tar": "https://git.astronand.dev/Hyperion/HyperionOS/archive/Hyperion-firmware-cct-v1.0.0.tar",
|
|
||||||
"BuildCommand": "sudo cp boot/cct/eeprom /dev/eeprom"
|
|
||||||
}
|
|
||||||
@@ -1,312 +0,0 @@
|
|||||||
--:Minify:--
|
|
||||||
local BOOT_DRIVE_PATH="/$"
|
|
||||||
local term = term
|
|
||||||
local os = os
|
|
||||||
-- Function to write text to the terminal with special character handling
|
|
||||||
local function write(text)
|
|
||||||
local x, y = term.getCursorPos()
|
|
||||||
local w, h = term.getSize()
|
|
||||||
|
|
||||||
for i = 1, #text do
|
|
||||||
local c = text:sub(i, i)
|
|
||||||
|
|
||||||
if c == "\n" then
|
|
||||||
y = y + 1
|
|
||||||
x = 1
|
|
||||||
elseif c == "\t" then
|
|
||||||
local tabSize = 4
|
|
||||||
local spaces = tabSize - ((x - 1) % tabSize)
|
|
||||||
term.write(string.rep(" ", spaces))
|
|
||||||
x = x + spaces
|
|
||||||
elseif c == "\b" then
|
|
||||||
if x > 1 then
|
|
||||||
x = x - 1
|
|
||||||
term.setCursorPos(x, y)
|
|
||||||
term.write(" ")
|
|
||||||
term.setCursorPos(x, y)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
if x <= w and y <= h then
|
|
||||||
term.setCursorPos(x, y)
|
|
||||||
term.write(c)
|
|
||||||
x = x + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Handle wrapping if we go past right edge
|
|
||||||
if x > w then
|
|
||||||
x = 1
|
|
||||||
y = y + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Handle scrolling if we go past bottom
|
|
||||||
if y-1 > h then
|
|
||||||
term.scroll(1)
|
|
||||||
y = h
|
|
||||||
term.setCursorPos(x, y)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
term.setCursorPos(x, y)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Function to display critical errors and halt the system
|
|
||||||
local function displaySuperBadError(err)
|
|
||||||
term.setBackgroundColor(0x1)
|
|
||||||
term.setTextColor(0x4)
|
|
||||||
term.clear()
|
|
||||||
term.setCursorPos(1, 1)
|
|
||||||
term.write("A critical error occurred while loading the system:")
|
|
||||||
term.setCursorPos(1, 3)
|
|
||||||
write(err)
|
|
||||||
while true do end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Wrap all in xpcall to catch errors
|
|
||||||
local ok, err = xpcall(function()
|
|
||||||
local apis={}
|
|
||||||
|
|
||||||
-- List of standard Lua globals
|
|
||||||
local lua = {
|
|
||||||
coroutine = true,
|
|
||||||
debug = true,
|
|
||||||
_HOST = true,
|
|
||||||
_VERSION = true,
|
|
||||||
assert = true,
|
|
||||||
collectgarbage = true,
|
|
||||||
error = true,
|
|
||||||
gcinfo = true,
|
|
||||||
getfenv = true,
|
|
||||||
getmetatable = true,
|
|
||||||
ipairs = true,
|
|
||||||
__inext = true,
|
|
||||||
load = true,
|
|
||||||
math = true,
|
|
||||||
next = true,
|
|
||||||
pairs = true,
|
|
||||||
pcall = true,
|
|
||||||
rawequal = true,
|
|
||||||
rawget = true,
|
|
||||||
rawlen = true,
|
|
||||||
rawset = true,
|
|
||||||
select = true,
|
|
||||||
setfenv = true,
|
|
||||||
setmetatable = true,
|
|
||||||
string = true,
|
|
||||||
table = true,
|
|
||||||
tonumber = true,
|
|
||||||
tostring = true,
|
|
||||||
type = true,
|
|
||||||
xpcall = true,
|
|
||||||
_G=true
|
|
||||||
}
|
|
||||||
|
|
||||||
-- Move all non-Lua standard library globals into the apis table
|
|
||||||
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
|
|
||||||
|
|
||||||
function sleep(time)
|
|
||||||
local stoptime = apis.os.clock() + (time)
|
|
||||||
while stoptime > apis.os.clock() do end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Set up terminal default colors
|
|
||||||
apis.term.setPaletteColor(0x1, 0x000000) -- #000000
|
|
||||||
apis.term.setPaletteColor(0x2, 0xFFFFFF) -- #FFFFFF
|
|
||||||
apis.term.setPaletteColor(0x4, 0xFF0000) -- #FF0000
|
|
||||||
apis.term.setPaletteColor(0x8, 0x00FF00) -- #00FF00
|
|
||||||
apis.term.setPaletteColor(0x10, 0x0000FF) -- #0000FF
|
|
||||||
apis.term.setPaletteColor(0x20, 0x00FFFF) -- #00FFFF
|
|
||||||
apis.term.setPaletteColor(0x40, 0xFF00FF) -- #FF00FF
|
|
||||||
apis.term.setPaletteColor(0x80, 0xFFFF00) -- #FFFF00
|
|
||||||
apis.term.setPaletteColor(0x100, 0xFF6D00) -- #FF6D00
|
|
||||||
apis.term.setPaletteColor(0x200, 0x6DFF55) -- #6DFF55
|
|
||||||
apis.term.setPaletteColor(0x400, 0x24FFFF) -- #24FFFF
|
|
||||||
apis.term.setPaletteColor(0x800, 0x924900) -- #924900
|
|
||||||
apis.term.setPaletteColor(0x1000, 0x6D6D55) -- #6D6D55
|
|
||||||
apis.term.setPaletteColor(0x2000, 0xDBDBAA) -- #DBDBAA
|
|
||||||
apis.term.setPaletteColor(0x4000, 0x6D00FF) -- #6D00FF
|
|
||||||
apis.term.setPaletteColor(0x8000, 0xB6FF00) -- #B6FF00
|
|
||||||
|
|
||||||
-- Wrapper to read files
|
|
||||||
local function getFile(path)
|
|
||||||
local file = apis.fs.open(path, "r")
|
|
||||||
if not file then displaySuperBadError("Could not open file: "..path) end
|
|
||||||
local content = file.readAll()
|
|
||||||
file.close()
|
|
||||||
return content
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Load kernel first if it fails, we can't continue so we display an error
|
|
||||||
local Kernel = load(getFile(BOOT_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
|
|
||||||
|
|
||||||
-- Set up event queue
|
|
||||||
local eventQueue = {}
|
|
||||||
local lkeys={}
|
|
||||||
lkeys[apis.keys.enter]="\n"
|
|
||||||
lkeys[apis.keys.backspace]="\b"
|
|
||||||
lkeys[apis.keys.tab]="\t"
|
|
||||||
|
|
||||||
-- Function to queue events
|
|
||||||
local function queueEvent(event, ...)
|
|
||||||
table.insert(eventQueue, {event, ...})
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Set up computer api
|
|
||||||
local computer = {
|
|
||||||
time = function() return apis.os.epoch("utc") end,
|
|
||||||
clock = 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
|
|
||||||
}
|
|
||||||
|
|
||||||
-- Set up terminal colors
|
|
||||||
local icolors={
|
|
||||||
[0x1] =0, -- #000000
|
|
||||||
[0x2] =1, -- #FFFFFF
|
|
||||||
[0x4] =2, -- #FF0000
|
|
||||||
[0x8] =3, -- #00FF00
|
|
||||||
[0x10] =4, -- #0000FF
|
|
||||||
[0x20] =5, -- #00FFFF
|
|
||||||
[0x40] =6, -- #FF00FF
|
|
||||||
[0x80] =7, -- #FFFF00
|
|
||||||
[0x100] =8, -- #FF6D00
|
|
||||||
[0x200] =9, -- #6DFF55
|
|
||||||
[0x400] =10, -- #24FFFF
|
|
||||||
[0x800] =11, -- #924900
|
|
||||||
[0x1000] =12, -- #6D6D55
|
|
||||||
[0x2000] =13, -- #DBDBAA
|
|
||||||
[0x4000] =14, -- #6D00FF
|
|
||||||
[0x8000] =15 -- #B6FF00
|
|
||||||
}
|
|
||||||
|
|
||||||
local colors={
|
|
||||||
[0]=0x0001, -- #000000
|
|
||||||
0x0002, -- #FFFFFF
|
|
||||||
0x0004, -- #FF0000
|
|
||||||
0x0008, -- #00FF00
|
|
||||||
0x0010, -- #0000FF
|
|
||||||
0x0020, -- #00FFFF
|
|
||||||
0x0040, -- #FF00FF
|
|
||||||
0x0080, -- #FFFF00
|
|
||||||
0x0100, -- #FF6D00
|
|
||||||
0x0200, -- #6DFF55
|
|
||||||
0x0400, -- #24FFFF
|
|
||||||
0x0800, -- #924900
|
|
||||||
0x1000, -- #6D6D55
|
|
||||||
0x2000, -- #DBDBAA
|
|
||||||
0x4000, -- #6D00FF
|
|
||||||
0x8000 -- #B6FF00
|
|
||||||
}
|
|
||||||
|
|
||||||
apis.term.setBackgroundColor(0x1)
|
|
||||||
apis.term.setTextColor(0x1000)
|
|
||||||
apis.term.clear()
|
|
||||||
apis.term.setCursorPos(1, 1)
|
|
||||||
|
|
||||||
-- Make the kernel coroutine so we can hook its execution
|
|
||||||
local kernelCoro = coroutine.create(function()
|
|
||||||
local ok, err = xpcall(Kernel, debug.traceback, apis, initFs, "cct", "/sbin/init", {
|
|
||||||
print=function(_,text) write(text.."\n") end,
|
|
||||||
printInline=function(_,text) write(text) end,
|
|
||||||
clear=function() apis.term.clear() apis.term.setCursorPos(1,1) end,
|
|
||||||
setCursorPos=function(_,x,y) apis.term.setCursorPos(x,y) end,
|
|
||||||
getCursorPos=function() return apis.term.getCursorPos() end,
|
|
||||||
getSize=function() return apis.term.getSize() end,
|
|
||||||
setBackgroundColor=function(_,color) apis.term.setBackgroundColor(colors[color]) end,
|
|
||||||
setTextColor=function(_,color) apis.term.setTextColor(colors[color]) end,
|
|
||||||
getBackgroundColor=function() return icolors[apis.term.getBackgroundColor()] end,
|
|
||||||
getTextColor=function() return icolors[apis.term.getTextColor()] end
|
|
||||||
}, computer, fs, "$")
|
|
||||||
if not ok then
|
|
||||||
displaySuperBadError(err)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
-- Diffine a coroutine.resumeWithTimeout function to avoid hanging the system, time is in milliseconds
|
|
||||||
function coroutine.resumeWithTimeout(co, timeout, ...)
|
|
||||||
local startTime = computer.time()
|
|
||||||
debug.sethook(co, function()
|
|
||||||
if computer.time() > startTime + timeout then
|
|
||||||
return coroutine.yield("timeout")
|
|
||||||
end
|
|
||||||
end, "", 1000)
|
|
||||||
local ret = {coroutine.resume(co, ...)}
|
|
||||||
if ret[1] 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")
|
|
||||||
|
|
||||||
-- Main loop to run the kernel coroutine
|
|
||||||
while true do
|
|
||||||
local status, err = coroutine.resumeWithTimeout(kernelCoro, 50)
|
|
||||||
apis.os.queueEvent("NoSleep")
|
|
||||||
local exit = false
|
|
||||||
while not exit do
|
|
||||||
local event = {coroutine.yield()}
|
|
||||||
if event[1] == "char" then
|
|
||||||
queueEvent("keyTyped", 1, event[2])
|
|
||||||
elseif event[1] == "key" then
|
|
||||||
queueEvent("keyPressed", 1, event[2])
|
|
||||||
if lkeys[event[2]] then
|
|
||||||
queueEvent("keyTyped", 1, lkeys[event[2]])
|
|
||||||
end
|
|
||||||
elseif event[1] == "key_up" then
|
|
||||||
queueEvent("keyReleased", 1, event[2])
|
|
||||||
elseif event[1] == "disk" then
|
|
||||||
queueEvent("componentAdded", "disk")
|
|
||||||
elseif event[1] == "disk_eject" then
|
|
||||||
queueEvent("componentRemoved", "disk")
|
|
||||||
elseif event[1] == "NoSleep" then
|
|
||||||
exit=true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if status == "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
|
|
||||||
@@ -1,182 +0,0 @@
|
|||||||
--:Minify:--
|
|
||||||
local kernel=...
|
|
||||||
local apis=kernel.apis
|
|
||||||
local main=apis.term
|
|
||||||
local native=apis.peripheral
|
|
||||||
local sides = {"top", "bottom", "left", "right", "front", "back"}
|
|
||||||
|
|
||||||
local function getType(name)
|
|
||||||
if native.isPresent(name) then
|
|
||||||
return native.getType(name)
|
|
||||||
end
|
|
||||||
for n = 1, #sides do
|
|
||||||
local side = sides[n]
|
|
||||||
if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", name) then
|
|
||||||
return native.call(side, "getTypeRemote", name)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
local function getNames()
|
|
||||||
local names = {}
|
|
||||||
for n = 1, #sides do
|
|
||||||
local side = sides[n]
|
|
||||||
if native.isPresent(side) then
|
|
||||||
table.insert(names, side)
|
|
||||||
end
|
|
||||||
if native.hasType(side, "peripheral_hub") then
|
|
||||||
local hubSides = native.call(side, "getConnectedSides")
|
|
||||||
for _, hubSide in ipairs(hubSides) do
|
|
||||||
table.insert(names, hubSide)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return names
|
|
||||||
end
|
|
||||||
|
|
||||||
local function wrapPeripheral(name)
|
|
||||||
if native.isPresent(name) then
|
|
||||||
return wrapPeripheral(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, "wrapRemote", name)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
local colors={
|
|
||||||
[0]=0x000000, -- #000000
|
|
||||||
0xFFFFFF, -- #FFFFFF
|
|
||||||
0xFF0000, -- #FF0000
|
|
||||||
0x00FF00, -- #00FF00
|
|
||||||
0x0000FF, -- #0000FF
|
|
||||||
0x00FFFF, -- #00FFFF
|
|
||||||
0xFF00FF, -- #FF00FF
|
|
||||||
0xFFFF00, -- #FFFF00
|
|
||||||
0xFF6D00, -- #FF6D00
|
|
||||||
0x6DFF55, -- #6DFF55
|
|
||||||
0x24FFFF, -- #24FFFF
|
|
||||||
0x924900, -- #924900
|
|
||||||
0x6D6D55, -- #6D6D55
|
|
||||||
0xDBDBAA, -- #DBDBAA
|
|
||||||
0x6D00FF, -- #6D00FF
|
|
||||||
0xB6FF00 -- #B6FF00
|
|
||||||
}
|
|
||||||
|
|
||||||
local icolors={
|
|
||||||
[0x1] =0, -- #000000
|
|
||||||
[0x2] =1, -- #FFFFFF
|
|
||||||
[0x4] =2, -- #FF0000
|
|
||||||
[0x8] =3, -- #00FF00
|
|
||||||
[0x10] =4, -- #0000FF
|
|
||||||
[0x20] =5, -- #00FFFF
|
|
||||||
[0x40] =6, -- #FF00FF
|
|
||||||
[0x80] =7, -- #FFFF00
|
|
||||||
[0x100] =8, -- #FF6D00
|
|
||||||
[0x200] =9, -- #6DFF55
|
|
||||||
[0x400] =10, -- #24FFFF
|
|
||||||
[0x800] =11, -- #924900
|
|
||||||
[0x1000] =12, -- #6D6D55
|
|
||||||
[0x2000] =13, -- #DBDBAA
|
|
||||||
[0x4000] =14, -- #6D00FF
|
|
||||||
[0x8000] =15 -- #B6FF00
|
|
||||||
}
|
|
||||||
|
|
||||||
local function 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
|
|
||||||
|
|
||||||
-- Handle wrapping if we go past right edge
|
|
||||||
if x > w then
|
|
||||||
x = 1
|
|
||||||
y = y + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Handle scrolling if we go past bottom
|
|
||||||
if y-1 > h then
|
|
||||||
term.scroll(1)
|
|
||||||
y = h
|
|
||||||
term.setCursorPos(x, y)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
term.setCursorPos(x, y)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function newTTY(term)
|
|
||||||
local ret={}
|
|
||||||
function ret.print(text)
|
|
||||||
write(text.."\n", term)
|
|
||||||
end
|
|
||||||
function ret.printInline(text)
|
|
||||||
write(text, term)
|
|
||||||
end
|
|
||||||
function ret.clear()
|
|
||||||
term.clear()
|
|
||||||
term.setCursorPos(1,1)
|
|
||||||
end
|
|
||||||
function ret.setCursorPos(x,y)
|
|
||||||
term.setCursorPos(x,y)
|
|
||||||
end
|
|
||||||
function ret.getCursorPos()
|
|
||||||
return term.getCursorPos()
|
|
||||||
end
|
|
||||||
function ret.getSize()
|
|
||||||
return term.getSize()
|
|
||||||
end
|
|
||||||
function ret.setBackgroundColor(color)
|
|
||||||
term.setBackgroundColor(colors[color])
|
|
||||||
end
|
|
||||||
function ret.setTextColor(color)
|
|
||||||
term.setTextColor(colors[color])
|
|
||||||
end
|
|
||||||
function ret.getBackgroundColor()
|
|
||||||
return icolors[term.getBackgroundColor()]
|
|
||||||
end
|
|
||||||
function ret.getTextColor()
|
|
||||||
return icolors[term.getTextColor()]
|
|
||||||
end
|
|
||||||
return ret
|
|
||||||
end
|
|
||||||
|
|
||||||
kernel.tty.register("tty0", newTTY(main))
|
|
||||||
|
|
||||||
for _, name in ipairs(getNames()) do
|
|
||||||
local t = getType(name)
|
|
||||||
if t == "monitor" then
|
|
||||||
local monitorTerm = wrapPeripheral(name)
|
|
||||||
monitorTerm.setTextScale(0.5)
|
|
||||||
kernel.tty.register(name, newTTY(monitorTerm))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
316
Src/Hyperion-firmware-cct/boot/cct/boot.lua
Normal file
316
Src/Hyperion-firmware-cct/boot/cct/boot.lua
Normal 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
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
--:Minify:--
|
--:Minify:--
|
||||||
sleep(1)
|
local BOOT_DRIVE_PATH=({...})[1] or "/$"
|
||||||
local BOOT_DRIVE_PATH="/$"
|
|
||||||
-- 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
|
||||||
@@ -16,7 +15,6 @@ local BOOT_DRIVE_PATH="/$"
|
|||||||
-- * `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
|
||||||
@@ -32,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
|
||||||
@@ -88,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
|
||||||
@@ -1,18 +1,17 @@
|
|||||||
--:Minify:--
|
-- :Minify:--
|
||||||
local apis = ({...})[1]
|
local apis = ({...})[1]
|
||||||
local BOOT_DRIVE_PATH="/$"
|
local BOOT_DRIVE_PATH = apis.BOOT_DRIVE_PATH or "/$"
|
||||||
local fs = apis.fs
|
local fs = apis.fs
|
||||||
local native = apis.peripheral
|
local native = apis.peripheral
|
||||||
local peripheral = {}
|
local peripheral = {}
|
||||||
local sides = {"top", "bottom", "left", "right", "front", "back"}
|
local sides = {"top", "bottom", "left", "right", "front", "back"}
|
||||||
|
|
||||||
function peripheral.getType(name)
|
function peripheral.getType(name)
|
||||||
if native.isPresent(name) then
|
if native.isPresent(name) then return native.getType(name) end
|
||||||
return native.getType(name)
|
|
||||||
end
|
|
||||||
for n = 1, #sides do
|
for n = 1, #sides do
|
||||||
local side = sides[n]
|
local side = sides[n]
|
||||||
if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", name) then
|
if native.hasType(side, "peripheral_hub") and
|
||||||
|
native.call(side, "isPresentRemote", name) then
|
||||||
return native.call(side, "getTypeRemote", name)
|
return native.call(side, "getTypeRemote", name)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -23,9 +22,7 @@ function peripheral.getNames()
|
|||||||
local names = {}
|
local names = {}
|
||||||
for n = 1, #sides do
|
for n = 1, #sides do
|
||||||
local side = sides[n]
|
local side = sides[n]
|
||||||
if native.isPresent(side) then
|
if native.isPresent(side) then table.insert(names, side) end
|
||||||
table.insert(names, side)
|
|
||||||
end
|
|
||||||
if native.hasType(side, "peripheral_hub") then
|
if native.hasType(side, "peripheral_hub") then
|
||||||
local hubSides = native.call(side, "getConnectedSides")
|
local hubSides = native.call(side, "getConnectedSides")
|
||||||
for _, hubSide in ipairs(hubSides) do
|
for _, hubSide in ipairs(hubSides) do
|
||||||
@@ -36,37 +33,30 @@ function peripheral.getNames()
|
|||||||
return names
|
return names
|
||||||
end
|
end
|
||||||
|
|
||||||
---------------------------------------------------------
|
local disks = {}
|
||||||
-- STORAGE
|
local internal = {}
|
||||||
---------------------------------------------------------
|
|
||||||
local disks = {} -- real disks
|
|
||||||
local internal = {} -- "$" disk
|
|
||||||
|
|
||||||
---------------------------------------------------------
|
|
||||||
-- HELPERS
|
|
||||||
---------------------------------------------------------
|
|
||||||
local function norm(path)
|
local function norm(path)
|
||||||
if not path or path == "" then return "/" end
|
if not path or path == "" then return "/" end
|
||||||
return fs.combine("/", path)
|
return fs.combine("/", path)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Creates a disk object given a base path
|
|
||||||
local function createDisk(id, basePath, readonly, periph)
|
local function createDisk(id, basePath, readonly, periph)
|
||||||
basePath = norm(basePath)
|
basePath = norm(basePath)
|
||||||
|
|
||||||
local disk = {address=id,isReadOnly=function() return readonly end}
|
local disk = {address = id, isReadOnly = function() return readonly end}
|
||||||
|
|
||||||
function disk:spaceUsed()
|
function disk:spaceUsed()
|
||||||
return fs.getCapacity(basePath) - fs.getFreeSpace(basePath)
|
return fs.getCapacity(basePath) - fs.getFreeSpace(basePath)
|
||||||
end
|
end
|
||||||
|
|
||||||
function disk:spaceTotal()
|
function disk:spaceTotal() return fs.getCapacity(basePath) end
|
||||||
return fs.getCapacity(basePath)
|
|
||||||
end
|
|
||||||
|
|
||||||
function disk:list(path)
|
function disk:list(path)
|
||||||
local p = fs.combine(basePath, path)
|
local p = fs.combine(basePath, path)
|
||||||
if not fs.exists(p) or not fs.isDir(p) then return nil, "not directory" end
|
if not fs.exists(p) or not fs.isDir(p) then
|
||||||
|
return nil, "not directory"
|
||||||
|
end
|
||||||
return fs.list(p)
|
return fs.list(p)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -103,13 +93,9 @@ local function createDisk(id, basePath, readonly, periph)
|
|||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
function disk:setLabel(label)
|
function disk:setLabel(label) periph.setLabel(label) end
|
||||||
periph.setLabel(label)
|
|
||||||
end
|
|
||||||
|
|
||||||
function disk:getLabel(label)
|
function disk:getLabel(label) return periph.getLabel() end
|
||||||
return periph.getLabel()
|
|
||||||
end
|
|
||||||
|
|
||||||
function disk:attributes(path)
|
function disk:attributes(path)
|
||||||
local p = fs.combine(basePath, path)
|
local p = fs.combine(basePath, path)
|
||||||
@@ -120,39 +106,30 @@ local function createDisk(id, basePath, readonly, periph)
|
|||||||
local p = fs.combine(basePath, path)
|
local p = fs.combine(basePath, path)
|
||||||
return fs.open(p, mode)
|
return fs.open(p, mode)
|
||||||
end
|
end
|
||||||
|
|
||||||
return disk
|
return disk
|
||||||
end
|
end
|
||||||
|
|
||||||
---------------------------------------------------------
|
|
||||||
-- INTERNAL DISK "$" (mapped to "/")
|
|
||||||
---------------------------------------------------------
|
|
||||||
internal["$"] = createDisk("$", BOOT_DRIVE_PATH, false, {
|
internal["$"] = createDisk("$", BOOT_DRIVE_PATH, false, {
|
||||||
setLabel=function(label)
|
setLabel = function(label)
|
||||||
local h = fs.open("/.label", "w")
|
local h = fs.open("/.label", "w")
|
||||||
h.write(label)
|
h.write(label)
|
||||||
h.close()
|
h.close()
|
||||||
end,
|
end,
|
||||||
getLabel=function()
|
getLabel = function()
|
||||||
local h = fs.open("/.label", "r")
|
local h = fs.open("/.label", "r")
|
||||||
|
if not h then return "$" end
|
||||||
local label = h.readAll()
|
local label = h.readAll()
|
||||||
h.close()
|
h.close()
|
||||||
return label
|
return label
|
||||||
end
|
end
|
||||||
})
|
})
|
||||||
|
|
||||||
---------------------------------------------------------
|
|
||||||
-- SCAN REAL DISK PERIPHERALS
|
|
||||||
---------------------------------------------------------
|
|
||||||
local function refresh()
|
local function refresh()
|
||||||
-- remove disks that no longer exist
|
|
||||||
for id, _ in pairs(disks) do
|
for id, _ in pairs(disks) do
|
||||||
if not peripheral.getType(id) then
|
if not peripheral.getType(id) then disks[id] = nil end
|
||||||
disks[id] = nil
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- detect new disks
|
|
||||||
for _, name in ipairs(peripheral.getNames()) do
|
for _, name in ipairs(peripheral.getNames()) do
|
||||||
if peripheral.getType(name) == "disk" then
|
if peripheral.getType(name) == "disk" then
|
||||||
if not disks[name] then
|
if not disks[name] then
|
||||||
@@ -165,28 +142,14 @@ local function refresh()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---------------------------------------------------------
|
|
||||||
-- ITERATOR
|
|
||||||
---------------------------------------------------------
|
|
||||||
local function iter()
|
local function iter()
|
||||||
refresh()
|
refresh()
|
||||||
-- first internal
|
|
||||||
local combined = {}
|
local combined = {}
|
||||||
|
|
||||||
for id, obj in pairs(internal) do
|
for id, obj in pairs(internal) do combined[id] = obj end
|
||||||
combined[id] = obj
|
for id, obj in pairs(disks) do combined[id] = obj end
|
||||||
end
|
|
||||||
for id, obj in pairs(disks) do
|
|
||||||
combined[id] = obj
|
|
||||||
end
|
|
||||||
|
|
||||||
return pairs(combined)
|
return pairs(combined)
|
||||||
end
|
end
|
||||||
|
|
||||||
---------------------------------------------------------
|
return {refresh = refresh, list = iter}
|
||||||
-- MODULE RETURN
|
|
||||||
---------------------------------------------------------
|
|
||||||
return {
|
|
||||||
refresh = refresh,
|
|
||||||
list = iter
|
|
||||||
}
|
|
||||||
363
Src/Hyperion-firmware-cct/lib/modules/CC-Tweaked/25_tty.kmod
Normal file
363
Src/Hyperion-firmware-cct/lib/modules/CC-Tweaked/25_tty.kmod
Normal file
@@ -0,0 +1,363 @@
|
|||||||
|
-- :Minify:--
|
||||||
|
local kernel = ...
|
||||||
|
local apis = kernel.apis
|
||||||
|
local native = apis.peripheral
|
||||||
|
local sides = {"top", "bottom", "left", "right", "front", "back"}
|
||||||
|
local peripheral={}
|
||||||
|
|
||||||
|
function peripheral.getNames()
|
||||||
|
local results = {}
|
||||||
|
for n = 1, #sides do
|
||||||
|
local side = sides[n]
|
||||||
|
if native.isPresent(side) then
|
||||||
|
table.insert(results, side)
|
||||||
|
if native.hasType(side, "peripheral_hub") then
|
||||||
|
local remote = native.call(side, "getNamesRemote")
|
||||||
|
for _, name in ipairs(remote) do
|
||||||
|
table.insert(results, name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return results
|
||||||
|
end
|
||||||
|
|
||||||
|
function peripheral.isPresent(name)
|
||||||
|
if native.isPresent(name) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
for n = 1, #sides do
|
||||||
|
local side = sides[n]
|
||||||
|
if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", name) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function peripheral.getType(peripheral)
|
||||||
|
if type(peripheral) == "string" then
|
||||||
|
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]
|
||||||
|
|
||||||
|
-- Update modifier keys
|
||||||
|
if eventType == "keyPressed" then
|
||||||
|
if charOrKey == apis.keys.leftCtrl or charOrKey == apis.keys.rightCtrl then
|
||||||
|
ctrl = true
|
||||||
|
elseif charOrKey == apis.keys.leftAlt or charOrKey == apis.keys.rightAlt then
|
||||||
|
alt = true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Handle Ctrl+C
|
||||||
|
if ctrl and charOrKey == apis.keys.c then
|
||||||
|
for _, task in ipairs(syscall.getTasks()) do
|
||||||
|
syscall.sigsend(task, 1) -- SIGINT
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
elseif eventType == "keyReleased" then
|
||||||
|
if charOrKey == apis.keys.leftCtrl or charOrKey == apis.keys.rightCtrl then
|
||||||
|
ctrl = false
|
||||||
|
elseif charOrKey == apis.keys.leftAlt or charOrKey == apis.keys.rightAlt then
|
||||||
|
alt = false
|
||||||
|
end
|
||||||
|
|
||||||
|
elseif eventType == "keyTyped" then
|
||||||
|
if charOrKey then fifo.push(charOrKey) end
|
||||||
|
end
|
||||||
|
|
||||||
|
timeout = false
|
||||||
|
else
|
||||||
|
timeout = true
|
||||||
|
end
|
||||||
|
|
||||||
|
if timeout then
|
||||||
|
sleep(0.05)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
newtty(apis.term, "TTY1", fifo.pop)
|
||||||
|
|
||||||
|
for i,v in ipairs({peripheral.find("monitor")}) do
|
||||||
|
v.setTextScale(.5)
|
||||||
|
v.write("Initializing...")
|
||||||
|
newtty(v,"TTY"..tostring(i+1),function () end)
|
||||||
|
end
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"Name": "Hyperion OC Firmware",
|
|
||||||
"Version": "1.0.0",
|
|
||||||
"Publishers": ["HyperionOS Dev Team"],
|
|
||||||
"Description": "The OpenComputers firmware package for HyperionOS.",
|
|
||||||
"Dependencies": [
|
|
||||||
"https://git.astronand.dev/Hyperion/HyperionOS/archive/Hyperion-kernel-v1.0.0.pkg"
|
|
||||||
],
|
|
||||||
"Tar": "https://git.astronand.dev/Hyperion/HyperionOS/archive/Hyperion-firmware-oc-v1.0.0.tar",
|
|
||||||
"BuildCommand": "cp boot/oc/eeprom /dev/eeprom"
|
|
||||||
}
|
|
||||||
@@ -32,6 +32,7 @@ local lua = {
|
|||||||
_G=true
|
_G=true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local apis={}
|
||||||
for i,v in pairs(_G) do
|
for i,v in pairs(_G) do
|
||||||
if not lua[i] or lua[i]==nil then
|
if not lua[i] or lua[i]==nil then
|
||||||
apis[i]=v
|
apis[i]=v
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"Name": "Hyperion Kernel",
|
|
||||||
"Version": "1.0.0",
|
|
||||||
"Publishers": ["HyperionOS Dev Team"],
|
|
||||||
"Description": "The kernel package for HyperionOS.",
|
|
||||||
"Dependencies": [],
|
|
||||||
"Tar": "https://git.astronand.dev/Hyperion/HyperionOS/archive/Hyperion-kernel-v1.0.0.tar"
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
U $;/
|
|
||||||
U devfs0000;/dev/
|
|
||||||
@@ -1,285 +0,0 @@
|
|||||||
--:Minify:--
|
|
||||||
function string.hasSuffix(str, suffix)
|
|
||||||
return string.sub(str, #suffix+1) == suffix
|
|
||||||
end
|
|
||||||
|
|
||||||
function string.hasPrefix(str, prefix)
|
|
||||||
return string.sub(str, 1, #prefix) == prefix
|
|
||||||
end
|
|
||||||
|
|
||||||
function string.getSuffix(str, prefix)
|
|
||||||
return string.sub(str, #prefix+1)
|
|
||||||
end
|
|
||||||
|
|
||||||
function string.getPrefix(str, suffix)
|
|
||||||
return string.sub(str, 1, #suffix)
|
|
||||||
end
|
|
||||||
|
|
||||||
function string.join(str, ...)
|
|
||||||
return table.concat(table.pack(str, ...))
|
|
||||||
end
|
|
||||||
|
|
||||||
function string.delim(str, ...)
|
|
||||||
return table.concat(table.pack(...), str)
|
|
||||||
end
|
|
||||||
|
|
||||||
function string.split(str, delim, maxResultCountOrNil)
|
|
||||||
assert(#delim == 1, "only delim len 1 supported for now")
|
|
||||||
maxResultCountOrNil = (maxResultCountOrNil or 0)-1
|
|
||||||
local rv = {}
|
|
||||||
local buf = ""
|
|
||||||
for i = 1, #str do
|
|
||||||
local c = string.sub(str,i,i)
|
|
||||||
if #rv ~= maxResultCountOrNil and c == delim then
|
|
||||||
table.insert(rv, buf)
|
|
||||||
buf = ""
|
|
||||||
else
|
|
||||||
buf = buf..c
|
|
||||||
end
|
|
||||||
end
|
|
||||||
table.insert(rv, buf)
|
|
||||||
return rv
|
|
||||||
end
|
|
||||||
|
|
||||||
function string.replace(str, search, replacement)
|
|
||||||
local rv = ""
|
|
||||||
local consumedLen = 1
|
|
||||||
local i = 1
|
|
||||||
while i<#str do
|
|
||||||
if string.sub(str, i, i+#search-1) == search then
|
|
||||||
rv = rv .. string.sub(str, consumedLen, i-1) .. replacement
|
|
||||||
i=i+#search
|
|
||||||
consumedLen = i
|
|
||||||
end
|
|
||||||
i=i+1
|
|
||||||
end
|
|
||||||
return rv .. string.sub(str, consumedLen)
|
|
||||||
end
|
|
||||||
|
|
||||||
function table.deepcopy(orig, copies)
|
|
||||||
copies = copies or {}
|
|
||||||
|
|
||||||
if type(orig) ~= 'table' then
|
|
||||||
return orig
|
|
||||||
elseif copies[orig] then
|
|
||||||
return copies[orig]
|
|
||||||
end
|
|
||||||
|
|
||||||
local copy = {}
|
|
||||||
copies[orig] = copy
|
|
||||||
|
|
||||||
for k, v in next, orig, nil do
|
|
||||||
local copied_key = table.deepcopy(k, copies)
|
|
||||||
local copied_val = table.deepcopy(v, copies)
|
|
||||||
copy[copied_key] = copied_val
|
|
||||||
end
|
|
||||||
|
|
||||||
return copy
|
|
||||||
end
|
|
||||||
|
|
||||||
function table.hasKey(tabl, query)
|
|
||||||
for i,v in pairs(tabl) do
|
|
||||||
if i==query then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function table.hasVal(tabl, query)
|
|
||||||
for i,v in pairs(tabl) do
|
|
||||||
if v==query then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function table.proxy(tbl)
|
|
||||||
local proxies = setmetatable({}, {__mode = "k"}) -- Weak table to avoid cycles
|
|
||||||
|
|
||||||
local function createProxy(t)
|
|
||||||
if type(t) ~= "table" then return t end
|
|
||||||
if proxies[t] then return proxies[t] end -- reuse proxy for the same table (handle cycles)
|
|
||||||
|
|
||||||
local proxy = {}
|
|
||||||
proxies[t] = proxy
|
|
||||||
|
|
||||||
local mt
|
|
||||||
mt = {
|
|
||||||
__index = function(_, k)
|
|
||||||
local value = t[k]
|
|
||||||
if type(value) == "table" then
|
|
||||||
return createProxy(value) -- recursively proxy subtables
|
|
||||||
else
|
|
||||||
return value
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
__newindex = function()
|
|
||||||
error("Attempt to modify table proxy", 2)
|
|
||||||
end,
|
|
||||||
__pairs = function()
|
|
||||||
return function(_, k)
|
|
||||||
local nextKey, nextValue = next(t, k)
|
|
||||||
if type(nextValue) == "table" then
|
|
||||||
nextValue = createProxy(nextValue)
|
|
||||||
end
|
|
||||||
return nextKey, nextValue
|
|
||||||
end, nil, nil
|
|
||||||
end,
|
|
||||||
__ipairs = function()
|
|
||||||
local i = 0
|
|
||||||
local n = #t
|
|
||||||
return function()
|
|
||||||
i = i + 1
|
|
||||||
if i <= n then
|
|
||||||
local v = t[i]
|
|
||||||
if type(v) == "table" then
|
|
||||||
v = createProxy(v)
|
|
||||||
end
|
|
||||||
return i, v
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
__metatable = false
|
|
||||||
}
|
|
||||||
|
|
||||||
setmetatable(proxy, mt)
|
|
||||||
return proxy
|
|
||||||
end
|
|
||||||
|
|
||||||
return createProxy(tbl)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function serialize(table)
|
|
||||||
local output = "{"
|
|
||||||
for i,v in pairs(table) do
|
|
||||||
local coma=true
|
|
||||||
if type(i) == "string" then
|
|
||||||
output=output.."[\""..i.."\"]="
|
|
||||||
elseif type(i) == "number" then
|
|
||||||
output=output.."["..tostring(i).."]="
|
|
||||||
end
|
|
||||||
if type(v) == "table" then
|
|
||||||
if v == table then
|
|
||||||
output=string.sub(output,1,#output-(#i+1))
|
|
||||||
coma=false
|
|
||||||
else
|
|
||||||
output=output..serialize(v)
|
|
||||||
end
|
|
||||||
elseif type(v) == "string" then
|
|
||||||
output=output.."[=["..v.."]=]"
|
|
||||||
elseif type(v) == "number" then
|
|
||||||
output=output..tostring(v)
|
|
||||||
elseif type(v) == "boolean" then
|
|
||||||
if v == true then
|
|
||||||
output=output.."true"
|
|
||||||
else
|
|
||||||
output=output.."false"
|
|
||||||
end
|
|
||||||
elseif type(v) == "function" then
|
|
||||||
output=output..tostring(v)
|
|
||||||
else
|
|
||||||
error("serialization of type \""..type(v).."\" is not supported")
|
|
||||||
end
|
|
||||||
if coma then
|
|
||||||
output=output..","
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if #table>0 or string.sub(output,#output,#output) == "," then
|
|
||||||
output=string.sub(output,1,#output-1)
|
|
||||||
end
|
|
||||||
output=output.."}"
|
|
||||||
return output
|
|
||||||
end
|
|
||||||
|
|
||||||
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)
|
|
||||||
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
|
|
||||||
|
|
||||||
syscall = setmetatable({}, {
|
|
||||||
__index = function(self, name)
|
|
||||||
return function(...)
|
|
||||||
local res = table.pack(coroutine.yield("syscall", name, ...))
|
|
||||||
if res[1] then
|
|
||||||
return table.unpack(res, 2, res.n)
|
|
||||||
else
|
|
||||||
error(res[2], 2)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
})
|
|
||||||
|
|
||||||
table.serialize=serialize
|
|
||||||
@@ -1,278 +0,0 @@
|
|||||||
--:Minify:--
|
|
||||||
local kernel = ...
|
|
||||||
local vfs = {}
|
|
||||||
vfs.mounts = { ["$"] = "/" }
|
|
||||||
local disks = kernel.disks
|
|
||||||
|
|
||||||
-- Path handling
|
|
||||||
local function normalizePath(path)
|
|
||||||
local parts = {}
|
|
||||||
for part in path:gmatch("[^/]+") do
|
|
||||||
if part == ".." then
|
|
||||||
if #parts > 0 then table.remove(parts) end
|
|
||||||
elseif part ~= "." and part ~= "" then
|
|
||||||
table.insert(parts, part)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return "/" .. table.concat(parts, "/") .. (path:sub(-1) == "/" and "/" or "")
|
|
||||||
end
|
|
||||||
|
|
||||||
local function resolvePath(path)
|
|
||||||
local task = kernel.currentTask
|
|
||||||
local cwd = task.cwd or "/"
|
|
||||||
|
|
||||||
if path:sub(1,1) ~= "/" then
|
|
||||||
path = cwd .. path
|
|
||||||
end
|
|
||||||
|
|
||||||
path = normalizePath(path)
|
|
||||||
|
|
||||||
local mountPoint = "/"
|
|
||||||
local mountId = "$"
|
|
||||||
for k,v in pairs(vfs.mounts) do
|
|
||||||
if path:sub(1,#v) == v and #v > #mountPoint then
|
|
||||||
mountPoint = v
|
|
||||||
mountId = k
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local diskPath = path:sub(#mountPoint + 1)
|
|
||||||
return disks[mountId], diskPath
|
|
||||||
end
|
|
||||||
|
|
||||||
-- File object creation
|
|
||||||
local function newFileObject(disk, handle, mode, path)
|
|
||||||
return {
|
|
||||||
disk = disk,
|
|
||||||
handle = handle,
|
|
||||||
mode = mode,
|
|
||||||
path = path,
|
|
||||||
refcount = 1
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
local function allocFD(task)
|
|
||||||
local fd = 0
|
|
||||||
while task.fd[fd] do fd = fd + 1 end
|
|
||||||
return fd
|
|
||||||
end
|
|
||||||
|
|
||||||
local function allocFD(task)
|
|
||||||
-- enforce per-task limit
|
|
||||||
local count = 0
|
|
||||||
for _ in pairs(task.fd) do count = count + 1 end
|
|
||||||
if count >= kernel.config.maxFilesPerTask then
|
|
||||||
error("EMFILE") -- Too many open files in this task
|
|
||||||
end
|
|
||||||
|
|
||||||
-- find first free FD
|
|
||||||
local fd = 0
|
|
||||||
while task.fd[fd] do fd = fd + 1 end
|
|
||||||
return fd
|
|
||||||
end
|
|
||||||
|
|
||||||
local function checkSystemLimit()
|
|
||||||
-- enforce system-wide limit
|
|
||||||
local total = 0
|
|
||||||
for _, task in pairs(kernel.tasks or {}) do
|
|
||||||
for _ in pairs(task.fd) do total = total + 1 end
|
|
||||||
end
|
|
||||||
if total >= kernel.config.maxOpenFiles then
|
|
||||||
error("ENFILE") -- Too many open files in the system
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- VFS syscalls
|
|
||||||
function vfs.open(path, mode)
|
|
||||||
local task = kernel.currentTask
|
|
||||||
|
|
||||||
-- check limits
|
|
||||||
checkSystemLimit()
|
|
||||||
|
|
||||||
local disk, diskPath = resolvePath(path)
|
|
||||||
if not disk then
|
|
||||||
error("No disk mounted for path '"..path.."'")
|
|
||||||
end
|
|
||||||
|
|
||||||
local handle = disk:open(diskPath, mode)
|
|
||||||
if not handle then return nil end
|
|
||||||
|
|
||||||
local file = newFileObject(disk.address, handle, mode, path)
|
|
||||||
local fd = allocFD(task)
|
|
||||||
task.fd[fd] = file
|
|
||||||
|
|
||||||
return fd
|
|
||||||
end
|
|
||||||
|
|
||||||
function vfs.close(fd)
|
|
||||||
local task = kernel.currentTask
|
|
||||||
local file = task.fd[fd]
|
|
||||||
if not file then error("EBADF") end
|
|
||||||
|
|
||||||
task.fd[fd] = nil
|
|
||||||
file.refcount = file.refcount - 1
|
|
||||||
if file.refcount == 0 then
|
|
||||||
file.handle.close()
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
function vfs.read(fd, count)
|
|
||||||
local file = kernel.currentTask.fd[fd]
|
|
||||||
if not file then error("EBADF") end
|
|
||||||
if not file.mode:find("r") then error("File not open for reading") end
|
|
||||||
return file.handle.read(count)
|
|
||||||
end
|
|
||||||
|
|
||||||
function vfs.write(fd, data)
|
|
||||||
local file = kernel.currentTask.fd[fd]
|
|
||||||
if not file then error("EBADF") end
|
|
||||||
if not file.mode:find("w") then error("File not open for writing") end
|
|
||||||
return file.handle.write(data)
|
|
||||||
end
|
|
||||||
|
|
||||||
function vfs.whereis(fd)
|
|
||||||
local file = kernel.currentTask.fd[fd]
|
|
||||||
if not file then error("EBADF") end
|
|
||||||
return file.path
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Filesystem operations
|
|
||||||
function vfs.mkdir(path)
|
|
||||||
local disk, diskPath = resolvePath(path)
|
|
||||||
if not disk then error("No disk mounted") end
|
|
||||||
return disk:makeDirectory(diskPath)
|
|
||||||
end
|
|
||||||
|
|
||||||
function vfs.remove(path)
|
|
||||||
local disk, diskPath = resolvePath(path)
|
|
||||||
if not disk then error("No disk mounted") end
|
|
||||||
return disk:remove(diskPath)
|
|
||||||
end
|
|
||||||
|
|
||||||
function vfs.attributes(path)
|
|
||||||
if type(path) == "number" then
|
|
||||||
local file = kernel.currentTask.fd[path]
|
|
||||||
if not file then error("EBADF") end
|
|
||||||
return disks[file.disk]:attributes(file.path)
|
|
||||||
end
|
|
||||||
local disk, diskPath = resolvePath(path)
|
|
||||||
if not disk then error("No disk mounted") end
|
|
||||||
return disk:attributes(diskPath)
|
|
||||||
end
|
|
||||||
|
|
||||||
function vfs.list(path)
|
|
||||||
local disk, diskPath = resolvePath(path)
|
|
||||||
if not disk then error("No disk mounted") end
|
|
||||||
return disk:list(diskPath)
|
|
||||||
end
|
|
||||||
|
|
||||||
function vfs.exists(path)
|
|
||||||
local disk, diskPath = resolvePath(path)
|
|
||||||
if not disk then return false end
|
|
||||||
return disk:directoryExists(diskPath) or disk:fileExists(diskPath)
|
|
||||||
end
|
|
||||||
|
|
||||||
function vfs.type(path)
|
|
||||||
if type(path) == "number" then
|
|
||||||
local file = kernel.currentTask.fd[path]
|
|
||||||
if not file then error("EBADF") end
|
|
||||||
return disks[file.disk]:type(file.path)
|
|
||||||
end
|
|
||||||
local disk, diskPath = resolvePath(path)
|
|
||||||
if not disk then error("No disk mounted") end
|
|
||||||
return disk:type(diskPath)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- CWD
|
|
||||||
function vfs.getcwd()
|
|
||||||
return kernel.currentTask.cwd
|
|
||||||
end
|
|
||||||
|
|
||||||
function vfs.setcwd(path)
|
|
||||||
if path:sub(-1) ~= "/" then path = path .. "/" end
|
|
||||||
if path:sub(1,1) ~= "/" then path = "/" .. path end
|
|
||||||
kernel.currentTask.cwd = normalizePath(path)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Mounting
|
|
||||||
function vfs.mount(diskId, path)
|
|
||||||
if kernel.uid ~= 0 then error("Permission denied") end
|
|
||||||
if not disks[diskId] then error("Unknown disk '"..diskId.."'") end
|
|
||||||
if path:sub(-1) ~= "/" then path = path .. "/" end
|
|
||||||
|
|
||||||
for _,v in pairs(vfs.mounts) do
|
|
||||||
if v == path then error("Mount point already used") end
|
|
||||||
end
|
|
||||||
vfs.mounts[diskId] = path
|
|
||||||
end
|
|
||||||
|
|
||||||
function vfs.unmount(path)
|
|
||||||
if kernel.uid ~= 0 then error("Permission denied") end
|
|
||||||
for k,v in pairs(vfs.mounts) do
|
|
||||||
if v == path then
|
|
||||||
vfs.mounts[k] = nil
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function vfs.getMounts()
|
|
||||||
return vfs.mounts
|
|
||||||
end
|
|
||||||
|
|
||||||
function vfs.virtdisk(obj)
|
|
||||||
kernel.disks[obj.address] = obj
|
|
||||||
kernel.log("Registered virtual disk at "..obj.address)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Redirect file operations to VFS
|
|
||||||
function vfs.dup(oldfd)
|
|
||||||
local task = kernel.currentTask
|
|
||||||
local file = task.fd[oldfd]
|
|
||||||
if not file then error("EBADF") end
|
|
||||||
|
|
||||||
local newfd = allocFD(task)
|
|
||||||
task.fd[newfd] = file
|
|
||||||
file.refcount = file.refcount + 1
|
|
||||||
return newfd
|
|
||||||
end
|
|
||||||
|
|
||||||
function vfs.dup2(oldfd, newfd)
|
|
||||||
local task = kernel.currentTask
|
|
||||||
local file = task.fd[oldfd]
|
|
||||||
if not file then error("EBADF") end
|
|
||||||
|
|
||||||
if oldfd == newfd then return newfd end
|
|
||||||
|
|
||||||
if task.fd[newfd] then
|
|
||||||
vfs.close(newfd)
|
|
||||||
end
|
|
||||||
|
|
||||||
task.fd[newfd] = file
|
|
||||||
file.refcount = file.refcount + 1
|
|
||||||
return newfd
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Syscall registration
|
|
||||||
kernel.vfs = vfs
|
|
||||||
kernel.syscalls["VFS_open"] = vfs.open
|
|
||||||
kernel.syscalls["VFS_read"] = vfs.read
|
|
||||||
kernel.syscalls["VFS_write"] = vfs.write
|
|
||||||
kernel.syscalls["VFS_close"] = vfs.close
|
|
||||||
kernel.syscalls["VFS_list"] = vfs.list
|
|
||||||
kernel.syscalls["VFS_type"] = vfs.type
|
|
||||||
kernel.syscalls["VFS_attributes"] = vfs.attributes
|
|
||||||
kernel.syscalls["VFS_mkdir"] = vfs.mkdir
|
|
||||||
kernel.syscalls["VFS_remove"] = vfs.remove
|
|
||||||
kernel.syscalls["VFS_exists"] = vfs.exists
|
|
||||||
kernel.syscalls["VFS_mount"] = vfs.mount
|
|
||||||
kernel.syscalls["VFS_unmount"] = vfs.unmount
|
|
||||||
kernel.syscalls["VFS_getcwd"] = vfs.getcwd
|
|
||||||
kernel.syscalls["VFS_setcwd"] = vfs.setcwd
|
|
||||||
kernel.syscalls["VFS_whereis"] = vfs.whereis
|
|
||||||
kernel.syscalls["VFS_dup"] = vfs.dup
|
|
||||||
kernel.syscalls["VFS_dup2"] = vfs.dup2
|
|
||||||
|
|
||||||
kernel.log("VFS module loaded")
|
|
||||||
@@ -1,173 +0,0 @@
|
|||||||
--:Minify:--
|
|
||||||
local kernel = ...
|
|
||||||
|
|
||||||
local proxy = {}
|
|
||||||
local data = {}
|
|
||||||
|
|
||||||
proxy.address = "devfs0000"
|
|
||||||
proxy.isReadOnly = false
|
|
||||||
proxy.spaceUsed = function() return 0 end
|
|
||||||
proxy.spaceTotal = function() return 0 end
|
|
||||||
proxy.makeDirectory = function() error("Permission denied") end
|
|
||||||
proxy.remove = function() error("Permission denied") end
|
|
||||||
proxy.setLabel = function() error("Permission denied") end
|
|
||||||
proxy.getLabel = function() return "devfs" end
|
|
||||||
proxy.attributes = function(path) return {
|
|
||||||
type = proxy.type(path),
|
|
||||||
isReadOnly = false,
|
|
||||||
size = 0,
|
|
||||||
lastModified = 0,
|
|
||||||
created = 0,
|
|
||||||
Permissions = "666",
|
|
||||||
owner = "root",
|
|
||||||
group = "root"
|
|
||||||
} end
|
|
||||||
|
|
||||||
local function getNode(path)
|
|
||||||
local parts = string.split(path, "/")
|
|
||||||
if parts[1] == "" then
|
|
||||||
table.remove(parts, 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
local node = data
|
|
||||||
for _, part in ipairs(parts) do
|
|
||||||
if node[part] then
|
|
||||||
node = node[part]
|
|
||||||
else
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return node
|
|
||||||
end
|
|
||||||
|
|
||||||
proxy.type = function(path)
|
|
||||||
local node = getNode(path)
|
|
||||||
if node then
|
|
||||||
return node.type
|
|
||||||
else
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
proxy.list = function(path)
|
|
||||||
local node = getNode(path)
|
|
||||||
if node and node.type == "directory" then
|
|
||||||
local content = table.keys(node)
|
|
||||||
table.remove(content, table.indexOf(content, "type"))
|
|
||||||
return content
|
|
||||||
else
|
|
||||||
error("Not a directory")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
proxy.open = function(path, mode)
|
|
||||||
local node = getNode(path)
|
|
||||||
if node and (node.type == "file" or node.type == "character device") then
|
|
||||||
if mode == "r" then
|
|
||||||
return {
|
|
||||||
read = node.read,
|
|
||||||
close = function() end
|
|
||||||
}
|
|
||||||
elseif mode == "w" then
|
|
||||||
return {
|
|
||||||
write = node.write,
|
|
||||||
close = function() end
|
|
||||||
}
|
|
||||||
else
|
|
||||||
error("Invalid mode")
|
|
||||||
end
|
|
||||||
else
|
|
||||||
error("Not a file"..type(node))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function newStringFile(content)
|
|
||||||
return {
|
|
||||||
type = "file",
|
|
||||||
read = function() return content end,
|
|
||||||
write = function(newContent) content = newContent end
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
local function newDirectory()
|
|
||||||
return {
|
|
||||||
type = "directory"
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
data["random"] = {
|
|
||||||
type = "character device",
|
|
||||||
read = function(amount)
|
|
||||||
local result = ""
|
|
||||||
for _ = 1, amount do
|
|
||||||
result = result .. string.char(math.random(0, 255))
|
|
||||||
end
|
|
||||||
return result
|
|
||||||
end,
|
|
||||||
write = function() error("Permission denied") end
|
|
||||||
}
|
|
||||||
|
|
||||||
data["null"] = {
|
|
||||||
type = "character device",
|
|
||||||
read = function() return "" end,
|
|
||||||
write = function() end
|
|
||||||
}
|
|
||||||
|
|
||||||
data["zero"] = {
|
|
||||||
type = "character device",
|
|
||||||
read = function(amount)
|
|
||||||
return string.rep("\0", amount)
|
|
||||||
end,
|
|
||||||
write = function() error("Permission denied") end
|
|
||||||
}
|
|
||||||
|
|
||||||
data["rtc"] = {
|
|
||||||
type = "character device",
|
|
||||||
read = function() return kernel.computer:time() end,
|
|
||||||
write = function() error("Permission denied") end
|
|
||||||
}
|
|
||||||
|
|
||||||
data["rtc0"] = {
|
|
||||||
type = "character device",
|
|
||||||
read = function() return kernel.computer:time() end,
|
|
||||||
write = function() error("Permission denied") end
|
|
||||||
}
|
|
||||||
|
|
||||||
data["eeprom"] = {
|
|
||||||
type = "character device",
|
|
||||||
read = function() return kernel.computer:getEEPROM() end,
|
|
||||||
write = function(data)
|
|
||||||
if kernel.uid ~= 0 then
|
|
||||||
error("Permission denied")
|
|
||||||
end
|
|
||||||
kernel.computer:setEEPROM(data)
|
|
||||||
end
|
|
||||||
}
|
|
||||||
|
|
||||||
local keyboard = kernel.newFifo()
|
|
||||||
local mouse = kernel.newFifo()
|
|
||||||
data["input"] = newDirectory()
|
|
||||||
data["input"]["keyboard"] = {
|
|
||||||
type = "pipe",
|
|
||||||
read = function(amount)
|
|
||||||
return keyboard.pop()
|
|
||||||
end,
|
|
||||||
write = function() error("Permission denied") end
|
|
||||||
}
|
|
||||||
data["input"]["mouse"] = {
|
|
||||||
type = "pipe",
|
|
||||||
read = function(amount)
|
|
||||||
return mouse.pop()
|
|
||||||
end,
|
|
||||||
write = function() error("Permission denied") end
|
|
||||||
}
|
|
||||||
|
|
||||||
data["pts"] = newDirectory()
|
|
||||||
|
|
||||||
kernel.devfs = {}
|
|
||||||
kernel.devfs.keyboard = keyboard
|
|
||||||
kernel.devfs.mouse = mouse
|
|
||||||
kernel.devfs.proxy = proxy
|
|
||||||
kernel.devfs.data = data
|
|
||||||
kernel.vfs.virtdisk(proxy)
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
--:Minify:--
|
|
||||||
local kernel = ...
|
|
||||||
local events = kernel.newFifo()
|
|
||||||
|
|
||||||
kernel.processes.keventd = function()
|
|
||||||
while true do
|
|
||||||
local event = {kernel.computer:getMachineEvent()}
|
|
||||||
if event[1] then
|
|
||||||
events.push(event)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
kernel.syscalls["IO_getEventAny"]=events.pop
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
--:Minify:--
|
|
||||||
local kernel=...
|
|
||||||
for i,v in ipairs(string.split(kernel.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)
|
|
||||||
if id~="$" then
|
|
||||||
kernel.vfs.mount(id,path)
|
|
||||||
end
|
|
||||||
::endline::
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
--:Minify:--
|
|
||||||
local kernel = ...
|
|
||||||
local ports = {}
|
|
||||||
local signals = {}
|
|
||||||
local ipc = {}
|
|
||||||
|
|
||||||
function ipc.open(port)
|
|
||||||
if not ports[port] then
|
|
||||||
local handle = kernel.newUUID()
|
|
||||||
ports[port] = {owner = kernel.currentProcess.pid, handle = handle, messages = {}}
|
|
||||||
return ports[port].handle
|
|
||||||
end
|
|
||||||
error("Port already opened")
|
|
||||||
end
|
|
||||||
|
|
||||||
function ipc.close(port)
|
|
||||||
if ports[port] then
|
|
||||||
if ports[port].owner == kernel.currentProcess.pid then
|
|
||||||
ports[port] = nil
|
|
||||||
return true
|
|
||||||
else
|
|
||||||
error("Cannot close port you do not own")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
error("Port not opened")
|
|
||||||
end
|
|
||||||
|
|
||||||
function ipc.send(port, message)
|
|
||||||
if ports[port] then
|
|
||||||
table.insert(ports[port].messages, {from = kernel.currentProcess.pid, message = message})
|
|
||||||
if signals[ports[port].owner] then
|
|
||||||
signals[ports[port].owner](port, message)
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
error("Port not opened")
|
|
||||||
end
|
|
||||||
|
|
||||||
function ipc.receive(port)
|
|
||||||
if ports[port] then
|
|
||||||
if #ports[port].messages > 0 then
|
|
||||||
return table.remove(ports[port].messages, 1)
|
|
||||||
else
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
error("Port not opened")
|
|
||||||
end
|
|
||||||
|
|
||||||
function ipc.setSignalHandler(pid, handler)
|
|
||||||
signals[pid] = handler
|
|
||||||
end
|
|
||||||
|
|
||||||
function ipc.clearSignalHandler(pid)
|
|
||||||
signals[pid] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
function ipc.sendSignal(pid, ...)
|
|
||||||
coroutine.resumeWithTimeout(coroutine.create(function(...)
|
|
||||||
if signals[pid] then
|
|
||||||
signals[pid](...)
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end), 100)
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
kernel.ipc = ipc
|
|
||||||
kernel.syscalls["ipc_open"] = ipc.open
|
|
||||||
kernel.syscalls["ipc_close"] = ipc.close
|
|
||||||
kernel.syscalls["ipc_send"] = ipc.send
|
|
||||||
kernel.syscalls["ipc_receive"] = ipc.receive
|
|
||||||
kernel.syscalls["ipc_setSignalHandler"] = ipc.setSignalHandler
|
|
||||||
kernel.syscalls["ipc_clearSignalHandler"] = ipc.clearSignalHandler
|
|
||||||
kernel.syscalls["ipc_sendSignal"] = ipc.sendSignal
|
|
||||||
kernel.log("Loaded IPC module")
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
--:Minify:--
|
|
||||||
local args={...}
|
|
||||||
local kernel=args[1]
|
|
||||||
kernel._G=_G
|
|
||||||
kernel._U=setmetatable({},{
|
|
||||||
__index = kernel._G,
|
|
||||||
__newindex = function(t,k,v)
|
|
||||||
if kernel.config.allowGlobalOverwrites or kernel.allowGlobalOverwrites then
|
|
||||||
rawset(t,k,v)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
error("Attempt to modify global variable '"..k.."'",2)
|
|
||||||
end,
|
|
||||||
__metatable = false
|
|
||||||
})
|
|
||||||
kernel.allowGlobalOverwrites=true
|
|
||||||
kernel._U._G=kernel._U
|
|
||||||
kernel.allowGlobalOverwrites=false
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
--:Minify:--
|
|
||||||
local kernel = ...
|
|
||||||
local pam = {}
|
|
||||||
kernel.pam = pam
|
|
||||||
local loggedIn = {}
|
|
||||||
|
|
||||||
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 blake2s = require("crypto.blake2s")
|
|
||||||
if not blake2s then error("Failed to load blake2s") end
|
|
||||||
|
|
||||||
if not kernel.vfs.exists("/etc/pam.d/secret") then
|
|
||||||
local key = ""
|
|
||||||
for i=1, 256 do
|
|
||||||
key=key..string.char(math.random(1,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")
|
|
||||||
|
|
||||||
function pam.authenticate(username, password)
|
|
||||||
local fpasswd = getFile("/etc/passwd")
|
|
||||||
local fshadow = getFile("/etc/shadow")
|
|
||||||
|
|
||||||
local passwdLines = string.split(fpasswd, "\n")
|
|
||||||
local shadowLines = string.split(fshadow, "\n")
|
|
||||||
|
|
||||||
local passwd = {}
|
|
||||||
local shadow = {}
|
|
||||||
for _, line in ipairs(passwdLines) do
|
|
||||||
local fields = string.split(line, ":")
|
|
||||||
passwd[fields[1]] = fields
|
|
||||||
end
|
|
||||||
for _, line in ipairs(shadowLines) do
|
|
||||||
local fields = string.split(line, ":")
|
|
||||||
shadow[fields[1]] = fields
|
|
||||||
end
|
|
||||||
|
|
||||||
for user, fields in pairs(passwd) do
|
|
||||||
if user == username then
|
|
||||||
local shadowPasswd = string.split(shadow[user][2], "$")
|
|
||||||
local salt = shadowPasswd[2]
|
|
||||||
local hashedPassword = blake2s(password .. salt, pepper)
|
|
||||||
if hashedPassword == shadowPasswd[3] then
|
|
||||||
loggedIn[username] = kernel.newUUID()
|
|
||||||
return loggedIn[username]
|
|
||||||
else
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function pam.authToken(username, token)
|
|
||||||
return loggedIn[username] == token
|
|
||||||
end
|
|
||||||
|
|
||||||
@@ -1,217 +0,0 @@
|
|||||||
--:Minify:--
|
|
||||||
local kernel = ...
|
|
||||||
local tasks = {}
|
|
||||||
local sys = {}
|
|
||||||
local nextpid = 2
|
|
||||||
kernel.exitMain=false
|
|
||||||
|
|
||||||
function sys.spawn(func, name, envars, args, tgid)
|
|
||||||
local id = nextpid
|
|
||||||
nextpid = nextpid + 1
|
|
||||||
tasks[tostring(id)] = {
|
|
||||||
coro=coroutine.create(function()
|
|
||||||
local ok, err = xpcall(func, debug.traceback, table.unpack(args or {}))
|
|
||||||
if not ok then
|
|
||||||
if kernel.config.logTaskExit then
|
|
||||||
kernel.log("Task "..tostring(id).." exited with err: "..tostring(err), "ERROR")
|
|
||||||
end
|
|
||||||
tasks[tostring(id)].status="Z"
|
|
||||||
tasks[tostring(id)].exit=tostring(err)
|
|
||||||
else
|
|
||||||
kernel.log("Task "..tostring(id).." exited with code: "..tostring(err), "INFO")
|
|
||||||
tasks[tostring(id)].status="Z"
|
|
||||||
tasks[tostring(id)].exit=err
|
|
||||||
end
|
|
||||||
end),
|
|
||||||
name=name or "task"..tostring(id),
|
|
||||||
envars=envars or {},
|
|
||||||
args=args or {},
|
|
||||||
status="R",
|
|
||||||
pid=id,
|
|
||||||
tgid=tgid or kernel.currentTask.tgid,
|
|
||||||
user=kernel.user,
|
|
||||||
uid=kernel.uid,
|
|
||||||
fd={},
|
|
||||||
exit="",
|
|
||||||
sleep=0,
|
|
||||||
ivs=0,
|
|
||||||
vs=0,
|
|
||||||
children={},
|
|
||||||
parent=kernel.currentTask,
|
|
||||||
siblings=kernel.currentTask.children,
|
|
||||||
syscallReturn={},
|
|
||||||
cwd=kernel.currentTask.cwd,
|
|
||||||
term=kernel.currentTask.term,
|
|
||||||
timeSlice=0,
|
|
||||||
lastTime=0,
|
|
||||||
totalTime=0,
|
|
||||||
numRuns=0
|
|
||||||
}
|
|
||||||
table.insert(kernel.currentTask.children, tasks[tostring(id)])
|
|
||||||
end
|
|
||||||
|
|
||||||
function sys.sleep(ms)
|
|
||||||
kernel.currentTask.status="S"
|
|
||||||
kernel.currentTask.sleep=kernel.computer:time()+ms
|
|
||||||
coroutine.yield()
|
|
||||||
end
|
|
||||||
|
|
||||||
kernel.syscalls["HPV_spawn"]=sys.spawn
|
|
||||||
kernel.syscalls["HPV_sleep"]=sys.sleep
|
|
||||||
kernel._G.sleep=function(...)coroutine.yield("syscall","HPV_sleep",...)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.term = nil
|
|
||||||
task.numRuns = nil
|
|
||||||
task.totalTime = nil
|
|
||||||
task.lastTime = nil
|
|
||||||
task.timeSlice = nil
|
|
||||||
task.syscallReturn = nil
|
|
||||||
task.sleep = nil
|
|
||||||
for k,v in pairs(task.fd) do
|
|
||||||
kernel.vfs.close(v)
|
|
||||||
end
|
|
||||||
task.fd = nil
|
|
||||||
task.reapTime = kernel.computer:time() + 30000
|
|
||||||
elseif task.reapTime and kernel.computer:time() > task.reapTime then
|
|
||||||
for _,child in ipairs(task.children) do
|
|
||||||
child.parent = tasks["1"]
|
|
||||||
child.siblings = tasks["1"].children
|
|
||||||
table.insert(tasks["1"].children, child)
|
|
||||||
end
|
|
||||||
for i, sibling in ipairs(task.siblings) do
|
|
||||||
if sibling.pid == task.pid then
|
|
||||||
table.remove(task.siblings, i)
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
tasks[pid] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local alpha = 0.85
|
|
||||||
local C_target = 0.01
|
|
||||||
local Tmin = 0.0005
|
|
||||||
local Tmax = 0.5
|
|
||||||
local lambda_budget = 0.08
|
|
||||||
local lambda_clamp = 0.03
|
|
||||||
local lambda_var = 0.02
|
|
||||||
local k_min = 0.5
|
|
||||||
local k_max = 0.5
|
|
||||||
local B = 0.01
|
|
||||||
|
|
||||||
function kernel.main()
|
|
||||||
while not kernel.exitMain do
|
|
||||||
local N = 0
|
|
||||||
local Tmin_hit = 0
|
|
||||||
local Tmax_hit = 0
|
|
||||||
local totalTaskTime = 0
|
|
||||||
local taskTimes = {}
|
|
||||||
|
|
||||||
for pid, task in pairs(tasks) do
|
|
||||||
if task.status == "S" then
|
|
||||||
if kernel.computer:time() >= task.sleep then
|
|
||||||
task.status="R"
|
|
||||||
task.sleep=0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if task.status == "R" then
|
|
||||||
kernel.currentTask = task
|
|
||||||
kernel.vfs.cwd = task.cwd
|
|
||||||
kernel.user = task.user
|
|
||||||
kernel.uid = task.uid
|
|
||||||
N = N + 1
|
|
||||||
|
|
||||||
-- assign adaptive time slice
|
|
||||||
task.timeSlice = math.min(Tmax, math.max(Tmin, B / (N ^ alpha)))
|
|
||||||
|
|
||||||
-- measure execution time
|
|
||||||
local startTime = kernel.computer:time()
|
|
||||||
local ret
|
|
||||||
if kernel.config.preempt then
|
|
||||||
ret = {coroutine.resumeWithTimeout(task.coro, task.timeSlice, table.unpack(task.syscallReturn))}
|
|
||||||
else
|
|
||||||
ret = {coroutine.resume(task.coro, table.unpack(task.syscallReturn))}
|
|
||||||
end
|
|
||||||
|
|
||||||
local elapsed = kernel.computer:time() - startTime
|
|
||||||
task.lastTime = elapsed
|
|
||||||
task.totalTime = (task.totalTime or 0) + elapsed
|
|
||||||
task.numRuns = (task.numRuns or 0) + 1
|
|
||||||
|
|
||||||
taskTimes[#taskTimes + 1] = elapsed
|
|
||||||
totalTaskTime = totalTaskTime + elapsed
|
|
||||||
|
|
||||||
if elapsed <= Tmin then Tmin_hit = Tmin_hit + 1 end
|
|
||||||
if elapsed >= Tmax then Tmax_hit = Tmax_hit + 1 end
|
|
||||||
|
|
||||||
-- handle task results
|
|
||||||
if ret[1] == "error" then
|
|
||||||
kernel.log("processHandlerException: "..ret[2])
|
|
||||||
task.status = "Z"
|
|
||||||
task.exit = "processHandlerException: "..ret[2]
|
|
||||||
elseif ret[1] == "timeout" then
|
|
||||||
task.ivs=task.ivs+1
|
|
||||||
task.syscallReturn = {}
|
|
||||||
elseif ret[1] == "success" then
|
|
||||||
task.vs=task.vs+1
|
|
||||||
if ret[2]=="syscall" then
|
|
||||||
if kernel.syscalls[ret[3]] then
|
|
||||||
if kernel.config.debugSyscalls then
|
|
||||||
kernel.log("Task "..task.pid.." invoking syscall: "..ret[3], "DBUG")
|
|
||||||
end
|
|
||||||
local sysret = {xpcall(kernel.syscalls[ret[3]], debug.traceback, table.unpack(ret, 4))}
|
|
||||||
if kernel.config.debugSyscalls then
|
|
||||||
if not sysret[1] then
|
|
||||||
kernel.log("Task "..task.pid.." syscall "..ret[3].." failed: "..tostring(sysret[2]))
|
|
||||||
else
|
|
||||||
kernel.log("Task "..task.pid.." syscall "..ret[3].." completed returning "..tostring(#sysret-1).." values", "DBUG")
|
|
||||||
for i=2,#sysret do
|
|
||||||
kernel.log(" retval["..tostring(i-1).."] = "..tostring(sysret[i]), "DBUG")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if not sysret[1] then
|
|
||||||
task.syscallReturn={false, sysret[2]}
|
|
||||||
else
|
|
||||||
task.syscallReturn={true, table.unpack(sysret,2)}
|
|
||||||
end
|
|
||||||
else
|
|
||||||
task.syscallReturn={false, "Unknown syscall: "..tostring(ret[3])}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local T_prev_avg = (N > 0) and (totalTaskTime / N) or 0
|
|
||||||
local T_prev_var = 0
|
|
||||||
for _, t in ipairs(taskTimes) do
|
|
||||||
T_prev_var = T_prev_var + (t - T_prev_avg)^2
|
|
||||||
end
|
|
||||||
if N > 0 then T_prev_var = T_prev_var / N end
|
|
||||||
|
|
||||||
if N > 0 then
|
|
||||||
local f_clamp = k_min*(Tmin_hit/N) - k_max*(Tmax_hit/N)
|
|
||||||
local B_budget = (C_target * (N^(alpha-1))) / math.max(T_prev_avg, 1e-8)
|
|
||||||
B = B + lambda_budget * (B_budget - B)
|
|
||||||
+ lambda_clamp * f_clamp
|
|
||||||
- lambda_var * T_prev_var
|
|
||||||
end
|
|
||||||
|
|
||||||
-- clean up dead tasks
|
|
||||||
reapDeadTasks()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
kernel.tasks=tasks
|
|
||||||
kernel.hpv=sys
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
--:Minify:--
|
|
||||||
local kernel=...
|
|
||||||
kernel.tty={}
|
|
||||||
kernel.tty.inst={}
|
|
||||||
|
|
||||||
function kernel.tty.register(tty, ttyo)
|
|
||||||
kernel.tty[tty]=ttyo
|
|
||||||
end
|
|
||||||
|
|
||||||
function kernel.tty.print(text)
|
|
||||||
local term=kernel.currentTask.term
|
|
||||||
if term and kernel.tty[term] then
|
|
||||||
kernel.tty[term].print(text)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function kernel.tty.printInline(text)
|
|
||||||
local term=kernel.currentTask.term
|
|
||||||
if term and kernel.tty[term] then
|
|
||||||
kernel.tty[term].printInline(text)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function kernel.tty.size()
|
|
||||||
local term=kernel.currentTask.term
|
|
||||||
if term and kernel.tty[term] then
|
|
||||||
return kernel.tty[term].size()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function kernel.tty.setCursorPos(x,y)
|
|
||||||
local term=kernel.currentTask.term
|
|
||||||
if term and kernel.tty[term] then
|
|
||||||
return kernel.tty[term].setCursorPos(x,y)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function kernel.tty.getCursorPos()
|
|
||||||
local term=kernel.currentTask.term
|
|
||||||
if term and kernel.tty[term] then
|
|
||||||
return kernel.tty[term].getCursorPos()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function kernel.tty.clear()
|
|
||||||
local term=kernel.currentTask.term
|
|
||||||
if term and kernel.tty[term] then
|
|
||||||
return kernel.tty[term].clear()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function kernel.tty.setTextColor(color)
|
|
||||||
local term=kernel.currentTask.term
|
|
||||||
if term and kernel.tty[term] then
|
|
||||||
return kernel.tty[term].setTextColor(color)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function kernel.tty.setBackgroundColor(color)
|
|
||||||
local term=kernel.currentTask.term
|
|
||||||
if term and kernel.tty[term] then
|
|
||||||
return kernel.tty[term].setBackgroundColor(color)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function kernel.tty.scroll(n)
|
|
||||||
local term=kernel.currentTask.term
|
|
||||||
if term and kernel.tty[term] then
|
|
||||||
return kernel.tty[term].scroll(n)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function kernel.tty.getTextColor()
|
|
||||||
local term=kernel.currentTask.term
|
|
||||||
if term and kernel.tty[term] and kernel.tty[term].getTextColor then
|
|
||||||
return kernel.tty[term].getTextColor()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function kernel.tty.getBackgroundColor()
|
|
||||||
local term=kernel.currentTask.term
|
|
||||||
if term and kernel.tty[term] and kernel.tty[term].getBackgroundColor then
|
|
||||||
return kernel.tty[term].getBackgroundColor()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function kernel.tty.bind(ttyid)
|
|
||||||
if not ttyid then
|
|
||||||
return false, "No TTY ID specified"
|
|
||||||
end
|
|
||||||
if not kernel.tty[ttyid] then
|
|
||||||
return false, "TTY "..tostring(ttyid).." not registered"
|
|
||||||
end
|
|
||||||
kernel.currentTask.term=ttyid
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
function kernel.tty.unbind()
|
|
||||||
kernel.currentTask.term=false
|
|
||||||
end
|
|
||||||
|
|
||||||
function kernel.tty.isBound()
|
|
||||||
return kernel.currentTask.term ~= nil
|
|
||||||
end
|
|
||||||
|
|
||||||
function kernel.tty.getBoundTTY()
|
|
||||||
return kernel.currentTask.term
|
|
||||||
end
|
|
||||||
|
|
||||||
kernel.syscalls["TTY_print"]=kernel.tty.print
|
|
||||||
kernel.syscalls["TTY_printInline"]=kernel.tty.printInline
|
|
||||||
kernel.syscalls["TTY_size"]=kernel.tty.size
|
|
||||||
kernel.syscalls["TTY_setCursorPos"]=kernel.tty.setCursorPos
|
|
||||||
kernel.syscalls["TTY_getCursorPos"]=kernel.tty.getCursorPos
|
|
||||||
kernel.syscalls["TTY_clear"]=kernel.tty.clear
|
|
||||||
kernel.syscalls["TTY_setTextColor"]=kernel.tty.setTextColor
|
|
||||||
kernel.syscalls["TTY_setBackgroundColor"]=kernel.tty.setBackgroundColor
|
|
||||||
kernel.syscalls["TTY_scroll"]=kernel.tty.scroll
|
|
||||||
kernel.syscalls["TTY_getTextColor"]=kernel.tty.getTextColor
|
|
||||||
kernel.syscalls["TTY_getBackgroundColor"]=kernel.tty.getBackgroundColor
|
|
||||||
kernel.syscalls["TTY_bind"]=kernel.tty.bind
|
|
||||||
kernel.syscalls["TTY_unbind"]=kernel.tty.unbind
|
|
||||||
kernel.syscalls["TTY_isBound"]=kernel.tty.isBound
|
|
||||||
kernel.syscalls["TTY_getBoundTTY"]=kernel.tty.getBoundTTY
|
|
||||||
|
|
||||||
kernel.log("TTY module loaded attempting to register console tty")
|
|
||||||
kernel.status="init"
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
--:Minify:--
|
|
||||||
local kernel=...
|
|
||||||
kernel.tty.bind("tty0")
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
--: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.TTY_print(output)
|
|
||||||
end
|
|
||||||
|
|
||||||
function printf(fmt, ...)
|
|
||||||
coroutine.yield()
|
|
||||||
local output=string.format(fmt,...)
|
|
||||||
syscall.TTY_print(output)
|
|
||||||
end
|
|
||||||
|
|
||||||
function printInline(...)
|
|
||||||
coroutine.yield()
|
|
||||||
local args={...}
|
|
||||||
local output=""
|
|
||||||
for i=1,#args do
|
|
||||||
output=output..tostring(args[i]).."\t"
|
|
||||||
end
|
|
||||||
output=output:sub(1,-2)
|
|
||||||
syscall.TTY_printInline(output)
|
|
||||||
end
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
--:Minify:--
|
|
||||||
local kernel = ...
|
|
||||||
kernel.log("Loading init system...")
|
|
||||||
kernel.log("InitPath: "..kernel.config.initPath)
|
|
||||||
local handle = kernel.vfs.open(kernel.config.initPath, "r")
|
|
||||||
local data = kernel.vfs.read(handle, 1024 * 1024 * 4)
|
|
||||||
kernel.vfs.close(handle)
|
|
||||||
|
|
||||||
local initFunc, err = load(data, "@sysinit")
|
|
||||||
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,
|
|
||||||
user="root",
|
|
||||||
uid=0,
|
|
||||||
fd={},
|
|
||||||
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...")
|
|
||||||
3
Src/Hyperion-kernel/boot/fstab
Normal file
3
Src/Hyperion-kernel/boot/fstab
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
U $;/
|
||||||
|
U devfs0000;/dev/
|
||||||
|
U tmpfs0000;/tmp/
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
--:Minify:--
|
-- :Minify:--
|
||||||
local fs = {}
|
local fs = {}
|
||||||
local disks = {}
|
local disks = {}
|
||||||
local mounts = {}
|
local mounts = {}
|
||||||
@@ -15,39 +15,42 @@ local function resolve(path)
|
|||||||
local newPath = path:sub(#mountPoint + 1)
|
local newPath = path:sub(#mountPoint + 1)
|
||||||
return disks[mounts[mountPoint]], newPath
|
return disks[mounts[mountPoint]], newPath
|
||||||
end
|
end
|
||||||
---------------------------------------------------------
|
|
||||||
|
|
||||||
function fs.update(initdisks)
|
function fs.update(initdisks)
|
||||||
disks = {}
|
disks = {}
|
||||||
for k, v in initdisks.list() do
|
for k, v in initdisks.list() do disks[k] = v end
|
||||||
disks[k] = v
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function fs.exists(path)
|
function fs.exists(path)
|
||||||
local disk, newPath = resolve(path)
|
local disk, newPath = resolve(path)
|
||||||
return disk:directoryExists(newPath) or disk:fileExists(newPath)
|
return disk:directoryExists(newPath) or disk:fileExists(newPath)
|
||||||
end
|
end
|
||||||
|
|
||||||
function fs.isFile(path)
|
function fs.isFile(path)
|
||||||
local disk, newPath = resolve(path)
|
local disk, newPath = resolve(path)
|
||||||
return disk:fileExists(newPath)
|
return disk:fileExists(newPath)
|
||||||
end
|
end
|
||||||
|
|
||||||
function fs.isDir(path)
|
function fs.isDir(path)
|
||||||
local disk, newPath = resolve(path)
|
local disk, newPath = resolve(path)
|
||||||
return disk:directoryExists(newPath)
|
return disk:directoryExists(newPath)
|
||||||
end
|
end
|
||||||
|
|
||||||
function fs.list(path)
|
function fs.list(path)
|
||||||
local disk, newPath = resolve(path)
|
local disk, newPath = resolve(path)
|
||||||
return disk:list(newPath)
|
return disk:list(newPath)
|
||||||
end
|
end
|
||||||
|
|
||||||
function fs.makeDir(path)
|
function fs.makeDir(path)
|
||||||
local disk, newPath = resolve(path)
|
local disk, newPath = resolve(path)
|
||||||
return disk:makeDirectory(newPath)
|
return disk:makeDirectory(newPath)
|
||||||
end
|
end
|
||||||
|
|
||||||
function fs.remove(path)
|
function fs.remove(path)
|
||||||
local disk, newPath = resolve(path)
|
local disk, newPath = resolve(path)
|
||||||
return disk:remove(newPath)
|
return disk:remove(newPath)
|
||||||
end
|
end
|
||||||
|
|
||||||
function fs.readAllText(path)
|
function fs.readAllText(path)
|
||||||
local disk, newPath = resolve(path)
|
local disk, newPath = resolve(path)
|
||||||
local handle = disk:open(newPath, "r")
|
local handle = disk:open(newPath, "r")
|
||||||
@@ -56,23 +59,26 @@ function fs.readAllText(path)
|
|||||||
handle.close()
|
handle.close()
|
||||||
return content
|
return content
|
||||||
end
|
end
|
||||||
|
|
||||||
function fs.writeAllText(path, text)
|
function fs.writeAllText(path, text)
|
||||||
local disk, newPath = resolve(path)
|
local disk, newPath = resolve(path)
|
||||||
local handle = disk:open(newPath, "w")
|
local handle = disk:open(newPath, "w")
|
||||||
handle.write(text)
|
handle.write(text)
|
||||||
handle.close()
|
handle.close()
|
||||||
end
|
end
|
||||||
|
|
||||||
function fs.appendAllText(path, text)
|
function fs.appendAllText(path, text)
|
||||||
local disk, newPath = resolve(path)
|
local disk, newPath = resolve(path)
|
||||||
local handle = disk:open(newPath, "a")
|
local handle = disk:open(newPath, "a")
|
||||||
handle.write(text)
|
handle.write(text)
|
||||||
handle.close()
|
handle.close()
|
||||||
end
|
end
|
||||||
function fs.load(path)
|
|
||||||
return load(fs.readAllText(path), path)
|
function fs.load(path) return load(fs.readAllText(path), path) end
|
||||||
end
|
|
||||||
function fs.mount(disk, mountPoint)
|
function fs.mount(disk, mountPoint)
|
||||||
if not disks[disk] then return end
|
if not disks[disk] then return end
|
||||||
mounts[mountPoint] = disk
|
mounts[mountPoint] = disk
|
||||||
end
|
end
|
||||||
return fs
|
|
||||||
|
return fs
|
||||||
@@ -6,12 +6,13 @@ local arch = args[3]
|
|||||||
local screen = args[5]
|
local screen = args[5]
|
||||||
local computer = args[6]
|
local computer = args[6]
|
||||||
local ifs = args[7]
|
local ifs = args[7]
|
||||||
local LOG_Text = ""
|
|
||||||
local kernel = {}
|
local kernel = {}
|
||||||
|
kernel.LOG_Text=""
|
||||||
|
kernel.version="HyperionOS V1.2.0"
|
||||||
kernel.process = "Kernel"
|
kernel.process = "Kernel"
|
||||||
kernel.user = "root"
|
kernel.users={[0]="root",[1]="User"}
|
||||||
kernel.group = "root"
|
kernel.hostname = "hyperion"
|
||||||
kernel.groups = {0}
|
kernel.groups = {}
|
||||||
kernel.uid = 0
|
kernel.uid = 0
|
||||||
kernel.gid = 0
|
kernel.gid = 0
|
||||||
kernel.status = "start"
|
kernel.status = "start"
|
||||||
@@ -24,14 +25,17 @@ kernel.sleep=sleep
|
|||||||
_G.sleep=nil
|
_G.sleep=nil
|
||||||
local windowsExp = false
|
local windowsExp = false
|
||||||
|
|
||||||
function kernel.log(msg, level)
|
function kernel.log(msg, level, c)
|
||||||
LOG_Text = LOG_Text..tostring(computer:time()).." "..kernel.user.." "..kernel.process.."["..tostring(level or "INFO").."]: "..msg.."\n"
|
c=c or 12
|
||||||
|
kernel.LOG_Text = kernel.LOG_Text..tostring(computer:time()).." "..kernel.users[kernel.uid].." "..kernel.process.."["..tostring(level or "INFO").."]: "..msg.."\n"
|
||||||
if kernel.status == "start" then
|
if kernel.status == "start" then
|
||||||
screen:print(tostring(computer:time()).." "..kernel.user.." "..kernel.process.."["..tostring(level or "INFO").."]: "..msg)
|
screen:setTextColor(c)
|
||||||
elseif kernel.status == "init" then
|
screen:print(string.format("%X",c-1).." "..tostring(computer:time()).." "..kernel.users[kernel.uid].." "..kernel.process.."["..tostring(level or "INFO").."]: "..msg)
|
||||||
|
elseif kernel.status == "term" then
|
||||||
kernel.standbyTask=kernel.currentTask
|
kernel.standbyTask=kernel.currentTask
|
||||||
kernel.currentTask=kernel.kernelTask
|
kernel.currentTask=kernel.kernelTask
|
||||||
kernel.tty.print(tostring(computer:time()).." "..kernel.user.." "..kernel.process.."["..tostring(level or "INFO").."]: "..msg)
|
kernel.vfs.devctl(1,"sfgc",c)
|
||||||
|
kernel.vfs.write(1,string.format("%X",c-1).." "..tostring(computer:time()).." "..kernel.users[kernel.uid].." "..kernel.process.."["..tostring(level or "INFO").."]: "..msg.."\n")
|
||||||
kernel.currentTask=kernel.standbyTask
|
kernel.currentTask=kernel.standbyTask
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -43,14 +47,15 @@ function kernel.PANIC(msg)
|
|||||||
kernel.status="Panic"
|
kernel.status="Panic"
|
||||||
kernel.reason=msg
|
kernel.reason=msg
|
||||||
screen:setTextColor(2)
|
screen:setTextColor(2)
|
||||||
screen:setBackgroundColor(0)
|
screen:setBackgroundColor(16)
|
||||||
screen:clear()
|
screen:clear()
|
||||||
screen:setCursorPos(1,1)
|
screen:setCursorPos(1,1)
|
||||||
screen:print(LOG_Text)
|
screen:print(kernel.LOG_Text)
|
||||||
screen:print("KERNEL PANIC!\n"..msg.."\nSystem halted.")
|
screen:print("KERNEL PANIC!\n"..msg.."\nSystem halted.")
|
||||||
screen:print("Press any key to continue...")
|
screen:print("Press any key to continue...")
|
||||||
|
kernel.exitMain = true
|
||||||
end
|
end
|
||||||
while true do
|
while true do
|
||||||
local event={computer:getMachineEvent()}
|
local event={computer:getMachineEvent()}
|
||||||
if event[1]=="keyPressed" then
|
if event[1]=="keyPressed" then
|
||||||
break
|
break
|
||||||
@@ -108,7 +113,7 @@ local split = function(str, delim, maxResultCountOrNil)
|
|||||||
end
|
end
|
||||||
|
|
||||||
if not ifs.isFile("/boot/boot.cfg") then
|
if not ifs.isFile("/boot/boot.cfg") then
|
||||||
kernel.log("boot.cfg missing or corrupted!, Attempting to write recovery boot.cfg", "ERROR")
|
kernel.log("boot.cfg missing or corrupted!, Attempting to write recovery boot.cfg", "ERROR", 2)
|
||||||
ifs.writeAllText("/boot/boot.cfg",ifs.readAllText("/boot/safeboot.cfg"))
|
ifs.writeAllText("/boot/boot.cfg",ifs.readAllText("/boot/safeboot.cfg"))
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -117,6 +122,7 @@ if not initCfgFunc then
|
|||||||
kernel.PANIC("Failed to load /boot/boot.cfg: "..tostring(err))
|
kernel.PANIC("Failed to load /boot/boot.cfg: "..tostring(err))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@diagnostic disable-next-line: param-type-mismatch
|
||||||
local initCfgStatus, config = pcall(initCfgFunc)
|
local initCfgStatus, config = pcall(initCfgFunc)
|
||||||
if not initCfgStatus then
|
if not initCfgStatus then
|
||||||
kernel.PANIC("Error in /boot/boot.cfg: "..tostring(config))
|
kernel.PANIC("Error in /boot/boot.cfg: "..tostring(config))
|
||||||
@@ -140,12 +146,9 @@ end
|
|||||||
kernel.log("Disks initialized")
|
kernel.log("Disks initialized")
|
||||||
|
|
||||||
function kernel.saveLog()
|
function kernel.saveLog()
|
||||||
ifs.writeAllText("/var/log/syslog.log", LOG_Text)
|
ifs.writeAllText("/var/log/syslog.log", kernel.LOG_Text)
|
||||||
end
|
end
|
||||||
|
|
||||||
ifs.remove("/tmp")
|
|
||||||
ifs.makeDir("/tmp")
|
|
||||||
|
|
||||||
function kernel.newFifo()
|
function kernel.newFifo()
|
||||||
local fifo = {}
|
local fifo = {}
|
||||||
fifo.push=function(data)
|
fifo.push=function(data)
|
||||||
@@ -181,9 +184,16 @@ end
|
|||||||
|
|
||||||
kernel.log("Gathering modules")
|
kernel.log("Gathering modules")
|
||||||
for _, i in ipairs(ifs.list("/lib/modules")) do
|
for _, i in ipairs(ifs.list("/lib/modules")) do
|
||||||
for _,v in ipairs(ifs.list("/lib/modules/"..i)) do
|
local modlist = ifs.list("/lib/modules/"..i)
|
||||||
local prior=tonumber(v:sub(1,2))
|
if not modlist then
|
||||||
modules[prior+1][#modules[prior+1]+1]="/lib/modules/"..i.."/"..v
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -201,7 +211,6 @@ kernel.kernelTask = {
|
|||||||
status="R",
|
status="R",
|
||||||
pid=0,
|
pid=0,
|
||||||
tgid=0,
|
tgid=0,
|
||||||
user="root",
|
|
||||||
uid=0,
|
uid=0,
|
||||||
fd={},
|
fd={},
|
||||||
exit="",
|
exit="",
|
||||||
@@ -218,21 +227,46 @@ kernel.kernelTask = {
|
|||||||
}
|
}
|
||||||
kernel.currentTask = kernel.kernelTask
|
kernel.currentTask = kernel.kernelTask
|
||||||
|
|
||||||
kernel.syscalls["OS_time"]=function() return kernel.computer:time() end
|
function kernel.shutdown()
|
||||||
kernel.syscalls["OS_log"]=kernel.log
|
kernel.computer:shutdown()
|
||||||
|
end
|
||||||
|
|
||||||
|
function kernel.reboot()
|
||||||
|
kernel.computer:reboot()
|
||||||
|
end
|
||||||
|
|
||||||
|
kernel.syscalls["time"]=function() return kernel.computer:time() end
|
||||||
|
kernel.syscalls["log"]=kernel.log
|
||||||
|
kernel.syscalls["getUptime"]=function() return kernel.computer:clock() end
|
||||||
|
kernel.syscalls["getUsername"]=function(uid) return kernel.users[uid or kernel.uid] end
|
||||||
|
kernel.syscalls["getHostname"]=function() return kernel.hostname end
|
||||||
|
kernel.syscalls["getHost"]=function() return kernel.apis._HOST end
|
||||||
|
kernel.syscalls["version"]=function() return kernel.version end
|
||||||
|
kernel.syscalls["setHostname"]=function(name) if kernel.uid~=0 then error("Permission denied") end kernel.hostname=name end
|
||||||
|
kernel.syscalls["arch"]=function() return arch end
|
||||||
|
kernel.syscalls["sysdump"]=function()
|
||||||
|
local rv={}
|
||||||
|
for i,v in pairs(kernel.syscalls) do
|
||||||
|
rv[#rv+1] = i
|
||||||
|
end
|
||||||
|
return rv
|
||||||
|
end
|
||||||
|
kernel.syscalls["test"]=function() return true end
|
||||||
|
|
||||||
kernel.log("Running modules")
|
kernel.log("Running modules")
|
||||||
for _,p in ipairs(modules) do
|
for _,p in ipairs(modules) do
|
||||||
for _,v in ipairs(p) 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)
|
local code=ifs.readAllText(v)
|
||||||
if not code then
|
if not code then
|
||||||
kernel.log("ModuReadErr: "..v, "WARN")
|
kernel.log("ModuReadErr: "..v, "WARN", 8)
|
||||||
goto skip
|
goto skip
|
||||||
end
|
end
|
||||||
local func,err=load(code,"@"..v)
|
local func,err=load(code,"@"..v)
|
||||||
if not func then kernel.panic("ModuLoadErr: "..tostring(err)) goto skip end
|
if not func then kernel.panic("ModuLoadErr: "..tostring(err)) goto skip end
|
||||||
local status, err = xpcall(func,debug.traceback, kernel)
|
local status, err = xpcall(func,debug.traceback, kernel)
|
||||||
if not status then kernel.panic("ModuRunErr: "..tostring(err)) end
|
if not status then kernel.panic("ModuRunErr: "..tostring(err)) end
|
||||||
|
if kernel.config.showModLoad then kernel.log("Loaded module "..v, "DBUG", 5) end
|
||||||
::skip::
|
::skip::
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -240,4 +274,7 @@ end
|
|||||||
kernel.log("Kernel initialized successfully.")
|
kernel.log("Kernel initialized successfully.")
|
||||||
kernel.status="running"
|
kernel.status="running"
|
||||||
kernel.main()
|
kernel.main()
|
||||||
|
if kernel.status=="panic" then
|
||||||
|
kernel.panic()
|
||||||
|
end
|
||||||
kernel.PANIC("Execution complete")
|
kernel.PANIC("Execution complete")
|
||||||
1
Src/Hyperion-kernel/etc/passwd
Normal file
1
Src/Hyperion-kernel/etc/passwd
Normal file
@@ -0,0 +1 @@
|
|||||||
|
0:0:root:/root:/bin/hysh
|
||||||
223
Src/Hyperion-kernel/lib/modules/hyperion/01_stdlib.kmod
Normal file
223
Src/Hyperion-kernel/lib/modules/hyperion/01_stdlib.kmod
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
-- :Minify:--
|
||||||
|
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 string.replace(s, target, repl)
|
||||||
|
local result = {}
|
||||||
|
local i = 1
|
||||||
|
local n = #s
|
||||||
|
local t_len = #target
|
||||||
|
|
||||||
|
while i <= n do
|
||||||
|
local match = true
|
||||||
|
if i + t_len - 1 <= n then
|
||||||
|
for j = 1, t_len do
|
||||||
|
if s:sub(i + j - 1, i + j - 1) ~= target:sub(j, j) then
|
||||||
|
match = false
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
match = false
|
||||||
|
end
|
||||||
|
|
||||||
|
if match then
|
||||||
|
table.insert(result, repl)
|
||||||
|
i = i + t_len
|
||||||
|
else
|
||||||
|
table.insert(result, s:sub(i, i))
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return table.concat(result)
|
||||||
|
end
|
||||||
|
|
||||||
|
function toHex(num)
|
||||||
|
return string.format("%X", num)
|
||||||
|
end
|
||||||
|
|
||||||
|
syscall = setmetatable({}, {
|
||||||
|
__index = function(self, name)
|
||||||
|
return function(...)
|
||||||
|
local res = table.pack(coroutine.yield("syscall", name, ...))
|
||||||
|
if res[1] then
|
||||||
|
return table.unpack(res, 2, res.n)
|
||||||
|
else
|
||||||
|
error(res[2], 2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
})
|
||||||
|
|
||||||
|
table.serialize = serialize
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user