70 Commits

Author SHA1 Message Date
6694711423 Update contributors.md
Added developer names
2026-02-22 23:05:52 -05:00
40c97ca000 Hyperion v1.2.0 2026-02-22 21:53:02 -06:00
dd2437d4af fix astro's syscall redefiniton so files use it 2026-02-21 14:12:05 -06:00
d026cfbb03 cleaned syscalls 2026-02-21 15:06:54 -05:00
aad7efd055 Merge pull request 'New build system + hysh functionality' (#4) from spsf/HyperionOS:main into main
Reviewed-on: #4
2026-02-21 14:54:01 -05:00
93c3bab263 allow hypervisor to kill non-owned processes 2026-02-21 14:09:12 -05:00
5bd16b8fd4 su kill error handling 2026-02-21 14:07:35 -05:00
3cc1459769 New build system + hysh functionality 2026-02-21 12:49:57 -06:00
0655f2a39e added users (spsf untangled by astronand) 2026-02-20 23:32:05 -05:00
57b1d46837 Merge branch 'main' of https://git.astronand.dev/Hyperion/HyperionOS 2026-02-20 23:08:23 -05:00
875759022a fixed printInline & printf 2026-02-20 23:07:31 -05:00
Ryan T
bb14ea34c3 Merge branch 'main' of https://git.astronand.dev/Hyperion/HyperionOS 2026-02-20 15:23:48 -05:00
Ryan T
149fb18564 add a couple shell programs 2026-02-20 15:23:46 -05:00
10bc775e64 e 2026-02-20 11:34:53 -05:00
287b146621 Update Src/Hyperion-bash/bin/bash 2026-02-19 11:18:02 -05:00
Ryan T
371954373e Fix lua prompt and add arrow key support 2026-02-16 20:37:13 -05:00
Ryan T
ecef2c6cb0 Merge branch 'main' of https://git.astronand.dev/Hyperion/HyperionOS 2026-02-16 17:27:25 -05:00
Ryan T
6770533581 Add cd and ls support to shell 2026-02-16 17:27:23 -05:00
19a9c72c6d Merge branch 'main' of https://git.astronand.dev/Hyperion/HyperionOS 2026-02-15 01:08:57 -05:00
fdb18d4ac5 working on readme 2026-02-15 01:08:55 -05:00
Ryan T
303449e13c fix shell going crazy when hitting bottom of screen 2026-02-15 00:47:35 -05:00
b6d1b9398f fuck me i forgot a semicolon (return) 2026-02-14 23:44:03 -05:00
bb829cdd8e Merge branch 'main' of https://git.astronand.dev/Hyperion/HyperionOS 2026-02-14 23:31:48 -05:00
6b9bed5047 fixed task cleanup fd errors 2026-02-14 23:31:45 -05:00
Ryan T
753c34bffa create proper shell 2026-02-14 23:31:33 -05:00
403178c832 made dev files (includeing tern) also fixed differnt keyboard layouts hopefully 2026-02-13 17:01:00 -05:00
33cd291c21 finished vfs for a while 2026-02-12 11:43:41 -05:00
1c4f48bd65 added ps 2026-02-03 20:47:33 -05:00
ec5e63898d fully fixed ghxx exploit 2026-02-01 02:01:35 -05:00
ea2a0e0e94 fixed metafile and list bug 2026-02-01 01:40:28 -05:00
4f50d90b79 fixed ghxx exploit 2026-02-01 01:01:07 -05:00
bf1dc9da7a changed boot orders 2026-01-30 21:59:37 -05:00
1a455a6025 added getuid and made more docs and working on half functional lua 2026-01-30 19:38:50 -05:00
c9ac447484 added setuid syscall 2026-01-30 18:47:37 -05:00
cb73f49962 added file and folder perms 2026-01-30 18:29:01 -05:00
d9caf655fb did mutiple changes 2026-01-30 11:16:20 -05:00
1c3d2c8b48 vfs rewrite lol fml 2026-01-29 20:29:06 -05:00
9bd9cdaba4 fixed keys added more OS syscalls and added colored logging 2026-01-20 11:33:24 -05:00
72bfce7b08 added myself as a node 2026-01-19 10:56:09 -05:00
6e48fcd5b9 Update Src/Hyperion-kernel/lib/modules/Hyperion/90_init.kmod 2026-01-19 10:41:08 -05:00
2d98ff64ce fixed name 2026-01-19 10:16:44 -05:00
6a14b87f44 made log text part of kernel table for access from modules 2026-01-19 10:14:25 -05:00
63bcc2df5c restructure for spm 2026-01-18 22:14:15 -05:00
fd7ee1aa3b made vfs changes 2026-01-18 11:59:45 -05:00
1073362007 readme 2026-01-16 19:09:16 -05:00
da5ad3b5cb bashex 2026-01-16 18:52:14 -05:00
e77fbcaf5d Merge branch 'main' of https://git.astronand.dev/Hyperion/HyperionOS 2026-01-16 18:51:20 -05:00
111fe764f8 disable cursor blink 2026-01-16 18:51:19 -05:00
Ryan T
83857d7e87 New Bash 2026-01-16 18:44:51 -05:00
83814311e5 chris proofing the bootloader 2026-01-16 17:13:12 -05:00
55fdddeff8 hotfix 2026-01-16 16:55:41 -05:00
1b21c87654 ooga booga i cant make my own boot script - chris 2026-01-16 16:53:29 -05:00
df4823940d README 2026-01-16 16:35:32 -05:00
70532f6e2c added more hpv funcs and made primshell 2026-01-16 14:17:28 -05:00
bd8fe50770 made lua debugger stop crying and added more docs 2026-01-16 08:40:16 -05:00
e5d6ec9725 made ctrl, shift, and alt detection 2026-01-15 19:40:49 -05:00
c620c4f1ba fixed terminal color 2026-01-15 17:22:56 -05:00
6b48e80157 Merge branch 'main' of https://git.astronand.dev/Hyperion/HyperionOS 2026-01-15 17:05:24 -05:00
fbbb8b8d65 fixed task log 2026-01-15 17:05:21 -05:00
Ryan T
a88bdc9639 experimenting with events 2026-01-15 17:04:58 -05:00
96c22f5237 added task logging 2026-01-15 16:59:40 -05:00
231b0ced48 gitr ignore 2026-01-15 16:47:33 -05:00
4100ecf0a1 remove build 2026-01-15 16:47:18 -05:00
Ryan T
968f4c54d7 Create bash 2026-01-15 16:46:24 -05:00
70526c76ba git ignore 2026-01-15 16:44:40 -05:00
3a8be2bdb7 hotfix 2026-01-15 16:43:03 -05:00
efffc8f0d2 hotfix 2026-01-15 16:25:26 -05:00
b48f926053 made print log 2026-01-15 16:22:25 -05:00
7d8055a703 remove fshandles arg 2026-01-15 16:14:47 -05:00
1141193fc8 added events pirimitive 2026-01-15 16:12:28 -05:00
201 changed files with 11420 additions and 3597 deletions

9
.gitignore vendored Normal file
View File

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

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

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

BIN
Build.tar

Binary file not shown.

View File

@@ -1,17 +0,0 @@
print(".. *. ..")
print(" *= +@* +* ")
print(" .@#. -@@@= :#@. ")
print(" =@@+ *@@@# +@@= ")
print(" %@@%: *@@@# -%@@% ")
print(" :@@@@+ *@@@# .*@@@@: ")
print(" :*@@@%- *@@@# -@@@@*: ")
print(" =%@@#. *@@@# .#@@%= ")
print(" :=. :*@@= *@@@# =@@+: .=: ")
print(" %@#=..*# +@@@# #*..=#@# ")
print(" .@@@@+=# .%@%: #=+@@@@. ")
print(" .....=# -@= *+...:. ")
print(" -*%*-@= - =@-*%*- ")
print(" -@*. -@%. :%@- :*@- ")
print(" .#@#@* ")
print(" -#- ")
print(" ")

View File

@@ -1 +0,0 @@
print("hello from hello.lua")

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
sleep(1)local a="/$"local b={...}if _HOST:find("UnBIOS")then return end;local c={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 d={}for e in pairs(_G)do if not c[e]then table.insert(d,e)end end;for f,e in ipairs(d)do _G[e]=nil end;local g=_G.term.native()for f,h in ipairs{"nativePaletteColor","nativePaletteColour","screenshot"}do g[h]=_G.term[h]end;_G.term=g;if _G.http then _G.http.checkURL=_G.http.checkURLAsync;_G.http.websocket=_G.http.websocketAsync end;if _G.commands then _G.commands=_G.commands.native end;if _G.turtle then _G.turtle.native,_G.turtle.craft=nil end;local i={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 e,j in pairs(i)do for f,k in ipairs(j)do _G[e][k]=nil end end;_G._HOST=_G._HOST.." (UnBIOS)"local l=error;_G.error=function()end;_G.term.redirect=function()end;function _G.term.native()_G.term.native=nil;_G.term.redirect=nil;_G.error=l;term.setBackgroundColor(32768)term.setTextColor(1)term.setCursorPos(1,1)term.setCursorBlink(true)term.clear()local m=fs.open(a.."/boot/cct/boot.lua","r")if m==nil then term.setCursorBlink(false)term.setTextColor(16384)term.write("Could not find /boot/cct/boot.lua. UnBIOS cannot continue.")term.setCursorPos(1,2)term.write("Press any key to continue")coroutine.yield("key")os.shutdown()end;local n,o=loadstring(m.readAll(),"@bootloader")m.close()if n==nil then term.setCursorBlink(false)term.setTextColor(16384)term.write("Could not load /boot/cc/boot.lua. UnBIOS cannot continue.")term.setCursorPos(1,2)term.write(o)term.setCursorPos(1,3)term.write("Press any key to continue")coroutine.yield("key")os.shutdown()end;setfenv(n,_G)local p=os.shutdown;os.shutdown=function()os.shutdown=p;return n(table.unpack(b))end end;if debug then local function q(r,s,t,u)local v,w,x=1,debug.getupvalue(r[s],u)while w~=t and w~=nil do w,x=debug.getupvalue(r[s],v)v=v+1 end;r[s]=x or r[s]end;q(_G,"loadstring","nativeloadstring",1)q(_G,"load","nativeload",5)if http then q(http,"request","nativeHTTPRequest",3)end;q(os,"shutdown","nativeShutdown",1)q(os,"reboot","nativeReboot",1)if turtle then q(turtle,"equipLeft","v",1)q(turtle,"equipRight","v",1)end;do local v,w,x=1,debug.getupvalue(peripheral.isPresent,2)while w~="native"and w~=nil do w,x=debug.getupvalue(peripheral.isPresent,v)v=v+1 end;_G.peripheral=x or peripheral end end

View File

@@ -1 +0,0 @@
local a=({...})[1]local b="/$"local c=a.fs;local d=a.peripheral;local e={}local f={"top","bottom","left","right","front","back"}function e.getType(g)if d.isPresent(g)then return d.getType(g)end;for h=1,#f do local i=f[h]if d.hasType(i,"peripheral_hub")and d.call(i,"isPresentRemote",g)then return d.call(i,"getTypeRemote",g)end end;return nil end;function e.getNames()local j={}for h=1,#f do local i=f[h]if d.isPresent(i)then table.insert(j,i)end;if d.hasType(i,"peripheral_hub")then local k=d.call(i,"getConnectedSides")for l,m in ipairs(k)do table.insert(j,m)end end end;return j end;local n={}local o={}local function p(q)if not q or q==""then return"/"end;return c.combine("/",q)end;local function r(s,t,u,v)t=p(t)local disk={address=s,isReadOnly=function()return u end}function disk:spaceUsed()return c.getCapacity(t)-c.getFreeSpace(t)end;function disk:spaceTotal()return c.getCapacity(t)end;function disk:list(q)local w=c.combine(t,q)if not c.exists(w)or not c.isDir(w)then return nil,"not directory"end;return c.list(w)end;function disk:fileExists(q)local w=c.combine(t,q)return c.exists(w)and not c.isDir(w)end;function disk:directoryExists(q)local w=c.combine(t,q)return c.exists(w)and c.isDir(w)end;function disk:type(q)local w=c.combine(t,q)if not c.exists(w)then return nil elseif c.isDir(w)then return"directory"else return"file"end end;function disk:makeDirectory(q)local w=c.combine(t,q)c.makeDir(w)return true end;function disk:remove(q)local w=c.combine(t,q)if c.exists(w)then c.delete(w)end;return true end;function disk:setLabel(x)v.setLabel(x)end;function disk:getLabel(x)return v.getLabel()end;function disk:attributes(q)local w=c.combine(t,q)return c.attributes(w)end;function disk:open(q,y)local w=c.combine(t,q)return c.open(w,y)end;return disk end;o["$"]=r("$",b,false,{setLabel=function(x)local z=c.open("/.label","w")z.write(x)z.close()end,getLabel=function()local z=c.open("/.label","r")local x=z.readAll()z.close()return x end})local function A()for s,l in pairs(n)do if not e.getType(s)then n[s]=nil end end;for l,g in ipairs(e.getNames())do if e.getType(g)=="disk"then if not n[g]then local B=disk.getMountPath(g)if B then n[g]=r(g,B,false,disk)end end end end end;local function C()A()local D={}for s,E in pairs(o)do D[s]=E end;for s,E in pairs(n)do D[s]=E end;return pairs(D)end;return{refresh=A,list=C}

View File

@@ -1,2 +0,0 @@
U $;/
U devfs0000;/dev/

View File

@@ -1 +0,0 @@
local a={}local b={}local c={}local function d(e)local f="/"for g,h in pairs(c)do if e:sub(1,#g)==g then if not f or#g>#f then f=g end end end;local i=e:sub(#f+1)return b[c[f]],i end;function a.update(j)b={}for k,l in j.list()do b[k]=l end end;function a.exists(e)local h,i=d(e)return h:directoryExists(i)or h:fileExists(i)end;function a.isFile(e)local h,i=d(e)return h:fileExists(i)end;function a.isDir(e)local h,i=d(e)return h:directoryExists(i)end;function a.list(e)local h,i=d(e)return h:list(i)end;function a.makeDir(e)local h,i=d(e)return h:makeDirectory(i)end;function a.remove(e)local h,i=d(e)return h:remove(i)end;function a.readAllText(e)local h,i=d(e)local m=h:open(i,"r")if not m then return nil end;local n=m.readAll()m.close()return n end;function a.writeAllText(e,o)local h,i=d(e)local m=h:open(i,"w")m.write(o)m.close()end;function a.appendAllText(e,o)local h,i=d(e)local m=h:open(i,"a")m.write(o)m.close()end;function a.load(e)return load(a.readAllText(e),e)end;function a.mount(h,f)if not b[h]then return end;c[f]=h end;return a

View File

@@ -1 +0,0 @@
local a={...}local b=a[1]local c=a[2]local d=a[3]local e=a[5]local f=a[6]local g=a[7]local h=""local i={}i.process="Kernel"i.user="root"i.group="root"i.groups={0}i.uid=0;i.gid=0;i.status="start"i.key={}i.cache={}i.cache.preload={}i._G=_G;i.sleep=sleep;_G.sleep=nil;local j=false;function i.log(k,l)h=h..tostring(f:time()).." "..i.user.." "..i.process.."["..tostring(l or"INFO").."]: "..k.."\n"if i.status=="start"then e:print(tostring(f:time()).." "..i.user.." "..i.process.."["..tostring(l or"INFO").."]: "..k)elseif i.status=="init"then i.standbyTask=i.currentTask;i.currentTask=i.kernelTask;i.tty.print(tostring(f:time()).." "..i.user.." "..i.process.."["..tostring(l or"INFO").."]: "..k)i.currentTask=i.standbyTask end end;function i.PANIC(k)if i.status~="Panic"then i.log("PANIC: "..k,"PANIC")pcall(i["saveLog"])i.status="Panic"i.reason=k;e:setTextColor(2)e:setBackgroundColor(0)e:clear()e:setCursorPos(1,1)e:print(h)e:print("KERNEL PANIC!\n"..k.."\nSystem halted.")e:print("Press any key to continue...")end;while true do local m={f:getMachineEvent()}if m[1]=="keyPressed"then break end end;f:reboot()end;i.panic=i.PANIC;if j then e:setTextColor(1)e:setBackgroundColor(4)e:clear()local n,o=e:getSize()e:setCursorPos(3,5)e:print(":(")e:setCursorPos(3,7)e:print("Your PC ran into a problem and needs to restart. We're just collecting some error")e:setCursorPos(3,8)e:print("info, and then we'll restart for you.\n")e:setCursorPos(3,o-5)e:print("Stop code: average windows experience")e:setCursorPos(1,o)e:print("Press any key to continue... jk reboot it yourself lazy")while true do end end;i.log("Kernel loaded.")i.log("Mounting init disks...")c.refresh()g.update(c)i.disks={}for p,q in c.list()do i.disks[q.address]=q end;g.mount("$","/")local r=g.readAllText("/boot/fstab")local s=function(t,u,v)assert(#u==1,"only delim len 1 supported for now")v=(v or 0)-1;local w={}local x=""for y=1,#t do local z=string.sub(t,y,y)if#w~=v and z==u then table.insert(w,x)x=""else x=x..z end end;table.insert(w,x)return w end;if not g.isFile("/boot/boot.cfg")then i.log("boot.cfg missing or corrupted!, Attempting to write recovery boot.cfg","ERROR")g.writeAllText("/boot/boot.cfg",g.readAllText("/boot/safeboot.cfg"))end;local A,B=load(g.readAllText("/boot/boot.cfg"),"@boot.cfg")if not A then i.PANIC("Failed to load /boot/boot.cfg: "..tostring(B))end;local C,D=pcall(A)if not C then i.PANIC("Error in /boot/boot.cfg: "..tostring(D))end;i.config=D;for y,q in ipairs(s(r,"\n"))do if q:sub(1,1)=="U"then local E=""for y=3,#q do if q:sub(y,y)==";"then if y==3 then i.log("Invalid fstab line... Skipping.","WARN")goto F end;E=q:sub(3,y-1)end end;local G=q:sub(#E+4)g.mount(E,G)::F::end end;i.log("Disks initialized")function i.saveLog()g.writeAllText("/var/log/syslog.log",h)end;g.remove("/tmp")g.makeDir("/tmp")function i.newFifo()local H={}H.push=function(I)table.insert(I)end;H.pop=function()return table.remove(H,1)end;return H end;function i.newUUID()local J="xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"local K=""for y=1,#J do local z=J:sub(y,y)if z=="x"then K=K..string.format("%x",math.random(0,15))elseif z=="y"then K=K..string.format("%x",math.random(8,11))else K=K..z end end;return K end;i.syscalls={}local L={[0]={}}for y=0,100 do L[y]={}end;i.log("Gathering modules")for p,y in ipairs(g.list("/lib/modules"))do for p,q in ipairs(g.list("/lib/modules/"..y))do local M=tonumber(q:sub(1,2))L[M+1][#L[M+1]+1]="/lib/modules/"..y.."/"..q end end;i.ifs=g;i.apis=b;i.computer=f;i.arch=d;i.initdisks=c;i.screen=e;i.processes={}i.fstab=r;i.kernelTask={name="kernel",status="R",pid=0,tgid=0,user="root",uid=0,fd={},exit="",sleep=0,ivs=0,vs=0,children={},syscallReturn={},cwd="/",timeSlice=0,lastTime=0,totalTime=0,numRuns=0}i.currentTask=i.kernelTask;i.syscalls["OS_time"]=function()return i.computer:time()end;i.syscalls["OS_log"]=i.log;i.log("Running modules")for p,N in ipairs(L)do for p,q in ipairs(N)do local O=g.readAllText(q)if not O then i.log("ModuReadErr: "..q,"WARN")goto P end;local Q,B=load(O,"@"..q)if not Q then i.panic("ModuLoadErr: "..tostring(B))goto P end;local R,B=xpcall(Q,debug.traceback,i)if not R then i.panic("ModuRunErr: "..tostring(B))end::P::end end;i.log("Kernel initialized successfully.")i.main()i.PANIC("Execution complete")

View File

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

View File

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

View File

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

View File

@@ -1,151 +0,0 @@
-- bit32.lua
-- Full pure-Lua implementation of Lua 5.2 bit32 library
-- NO Lua 5.3 operators used
local bit32 = {}
local MOD32 = 2^32
local MOD31 = 2^31
local function norm(x)
return x % MOD32
end
-- Convert number to bit table
local function tobits(x)
x = norm(x)
local t = {}
for i = 0, 31 do
local b = x % 2
t[i] = b
x = (x - b) / 2
end
return t
end
-- Convert bit table to number
local function frombits(t)
local x = 0
local p = 1
for i = 0, 31 do
if t[i] == 1 then
x = x + p
end
p = p * 2
end
return norm(x)
end
-- ===== Logical ops =====
function bit32.band(...)
local args = {...}
if #args == 0 then return 0xFFFFFFFF end
local bits = tobits(args[1])
for i = 2, #args do
local b = tobits(args[i])
for j = 0, 31 do
bits[j] = (bits[j] == 1 and b[j] == 1) and 1 or 0
end
end
return frombits(bits)
end
function bit32.bor(...)
local args = {...}
if #args == 0 then return 0 end
local bits = tobits(args[1])
for i = 2, #args do
local b = tobits(args[i])
for j = 0, 31 do
bits[j] = (bits[j] == 1 or b[j] == 1) and 1 or 0
end
end
return frombits(bits)
end
function bit32.bxor(...)
local args = {...}
if #args == 0 then return 0 end
local bits = tobits(args[1])
for i = 2, #args do
local b = tobits(args[i])
for j = 0, 31 do
bits[j] = (bits[j] ~= b[j]) and 1 or 0
end
end
return frombits(bits)
end
function bit32.bnot(x)
local bits = tobits(x)
for i = 0, 31 do
bits[i] = bits[i] == 1 and 0 or 1
end
return frombits(bits)
end
-- ===== Shifts =====
function bit32.lshift(x, n)
return norm(norm(x) * 2^n)
end
function bit32.rshift(x, n)
return math.floor(norm(x) / 2^n)
end
function bit32.arshift(x, n)
x = norm(x)
if x >= MOD31 then
return math.floor((x - MOD32) / 2^n)
else
return math.floor(x / 2^n)
end
end
-- ===== Rotates =====
function bit32.lrotate(x, n)
n = n % 32
x = norm(x)
local left = (x * 2^n) % MOD32
local right = math.floor(x / 2^(32 - n))
return norm(left + right)
end
function bit32.rrotate(x, n)
n = n % 32
x = norm(x)
local right = math.floor(x / 2^n)
local left = (x * 2^(32 - n)) % MOD32
return norm(left + right)
end
-- ===== Bit fields =====
function bit32.extract(x, field, width)
width = width or 1
return bit32.band(bit32.rshift(x, field), 2^width - 1)
end
function bit32.replace(x, v, field, width)
width = width or 1
local mask = bit32.lshift(2^width - 1, field)
x = bit32.band(x, bit32.bnot(mask))
return bit32.bor(x, bit32.lshift(v, field))
end
-- ===== 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

View File

@@ -1 +0,0 @@
local a=...local b=a.apis;local c=b.term;local d=b.peripheral;local e={"top","bottom","left","right","front","back"}local function f(g)if d.isPresent(g)then return d.getType(g)end;for h=1,#e do local i=e[h]if d.hasType(i,"peripheral_hub")and d.call(i,"isPresentRemote",g)then return d.call(i,"getTypeRemote",g)end end;return nil end;local function j()local k={}for h=1,#e do local i=e[h]if d.isPresent(i)then table.insert(k,i)end;if d.hasType(i,"peripheral_hub")then local l=d.call(i,"getConnectedSides")for m,n in ipairs(l)do table.insert(k,n)end end end;return k end;local function o(g)if d.isPresent(g)then return o(g)end;for h=1,#e do local i=e[h]if d.hasType(i,"peripheral_hub")and d.call(i,"isPresentRemote",g)then return d.call(i,"wrapRemote",g)end end;return nil end;local p={[0]=0x000000,0xFFFFFF,0xFF0000,0x00FF00,0x0000FF,0x00FFFF,0xFF00FF,0xFFFF00,0xFF6D00,0x6DFF55,0x24FFFF,0x924900,0x6D6D55,0xDBDBAA,0x6D00FF,0xB6FF00}local q={[0x1]=0,[0x2]=1,[0x4]=2,[0x8]=3,[0x10]=4,[0x20]=5,[0x40]=6,[0x80]=7,[0x100]=8,[0x200]=9,[0x400]=10,[0x800]=11,[0x1000]=12,[0x2000]=13,[0x4000]=14,[0x8000]=15}local function r(s,t)local u,v=t.getCursorPos()local w,x=t.getSize()for y=1,#s do local z=s:sub(y,y)if z=="\n"then v=v+1;u=1 elseif z=="\t"then local A=4;local B=A-(u-1)%A;t.write(string.rep(" ",B))u=u+B elseif z=="\b"then if u>1 then u=u-1;t.setCursorPos(u,v)t.write(" ")t.setCursorPos(u,v)end else if u<=w and v<=x then t.setCursorPos(u,v)t.write(z)u=u+1 end end;if u>w then u=1;v=v+1 end;if v-1>x then t.scroll(1)v=x;t.setCursorPos(u,v)end end;t.setCursorPos(u,v)end;local function C(t)local D={}function D.print(s)r(s.."\n",t)end;function D.printInline(s)r(s,t)end;function D.clear()t.clear()t.setCursorPos(1,1)end;function D.setCursorPos(u,v)t.setCursorPos(u,v)end;function D.getCursorPos()return t.getCursorPos()end;function D.getSize()return t.getSize()end;function D.setBackgroundColor(E)t.setBackgroundColor(p[E])end;function D.setTextColor(E)t.setTextColor(p[E])end;function D.getBackgroundColor()return q[t.getBackgroundColor()]end;function D.getTextColor()return q[t.getTextColor()]end;return D end;a.tty.register("tty0",C(c))for m,g in ipairs(j())do local F=f(g)if F=="monitor"then local G=o(g)G.setTextScale(0.5)a.tty.register(g,C(G))end end

View File

@@ -1 +0,0 @@
function string.hasSuffix(a,b)return string.sub(a,#b+1)==b end;function string.hasPrefix(a,c)return string.sub(a,1,#c)==c end;function string.getSuffix(a,c)return string.sub(a,#c+1)end;function string.getPrefix(a,b)return string.sub(a,1,#b)end;function string.join(a,...)return table.concat(table.pack(a,...))end;function string.delim(a,...)return table.concat(table.pack(...),a)end;function string.split(a,d,e)assert(#d==1,"only delim len 1 supported for now")e=(e or 0)-1;local f={}local g=""for h=1,#a do local i=string.sub(a,h,h)if#f~=e and i==d then table.insert(f,g)g=""else g=g..i end end;table.insert(f,g)return f end;function string.replace(a,j,k)local f=""local l=1;local h=1;while h<#a do if string.sub(a,h,h+#j-1)==j then f=f..string.sub(a,l,h-1)..k;h=h+#j;l=h end;h=h+1 end;return f..string.sub(a,l)end;function table.deepcopy(m,n)n=n or{}if type(m)~='table'then return m elseif n[m]then return n[m]end;local o={}n[m]=o;for p,q in next,m,nil do local r=table.deepcopy(p,n)local s=table.deepcopy(q,n)o[r]=s end;return o end;function table.hasKey(t,u)for h,q in pairs(t)do if h==u then return true end end;return false end;function table.hasVal(t,u)for h,q in pairs(t)do if q==u then return true end end;return false end;function table.proxy(v)local w=setmetatable({},{__mode="k"})local function x(y)if type(y)~="table"then return y end;if w[y]then return w[y]end;local z={}w[y]=z;local A;A={__index=function(B,p)local C=y[p]if type(C)=="table"then return x(C)else return C end end,__newindex=function()error("Attempt to modify table proxy",2)end,__pairs=function()return function(B,p)local D,E=next(y,p)if type(E)=="table"then E=x(E)end;return D,E end,nil,nil end,__ipairs=function()local h=0;local F=#y;return function()h=h+1;if h<=F then local q=y[h]if type(q)=="table"then q=x(q)end;return h,q end end end,__metatable=false}setmetatable(z,A)return z end;return x(v)end;local function G(table)local H="{"for h,q in pairs(table)do local I=true;if type(h)=="string"then H=H.."[\""..h.."\"]="elseif type(h)=="number"then H=H.."["..tostring(h).."]="end;if type(q)=="table"then if q==table then H=string.sub(H,1,#H-(#h+1))I=false else H=H..G(q)end elseif type(q)=="string"then H=H.."[=["..q.."]=]"elseif type(q)=="number"then H=H..tostring(q)elseif type(q)=="boolean"then if q==true then H=H.."true"else H=H.."false"end elseif type(q)=="function"then H=H..tostring(q)else error("serialization of type \""..type(q).."\" is not supported")end;if I then H=H..","end end;if#table>0 or string.sub(H,#H,#H)==","then H=string.sub(H,1,#H-1)end;H=H.."}"return H end;local J=type;local K=getmetatable;function type(L,M)if M then return J(L)end;if J(L)~="table"then return J(L)else if J(K(L))=="table"then local N=K(L)if N.__type then return N.__type end else return"table"end end end;function getmetatable(L)if J(L)~="table"then return end;if J(K(L))=="table"then if K(L).__isuserdata then if J(K(L).__usermeta)=="function"then return K(L).__usermeta()else return K(L).__usermeta end else return K(L)end else return K(L)end end;function isEqualToAny(O,...)local P={...}for h=0,#P do if O==P[h]then return true end end;return false end;function isEqualToAll(O,...)local P={...}for h=0,#P do if O~=P[h]then return false end end;return true end;function table.keys(y)local O={}for F in pairs(y)do table.insert(O,F)end;return O end;function table.values(y)local O={}for B,F in pairs(y)do table.insert(O,F)end;return O end;function table.indexOf(y,C)for h,q in ipairs(y)do if q==C then return h end end;return-1 end;syscall=setmetatable({},{__index=function(self,Q)return function(...)local R=table.pack(coroutine.yield("syscall",Q,...))if R[1]then return table.unpack(R,2,R.n)else error(R[2],2)end end end})table.serialize=G

View File

@@ -1 +0,0 @@
local a=...local b={}b.mounts={["$"]="/"}local c=a.disks;local function d(e)local f={}for g in e:gmatch("[^/]+")do if g==".."then if#f>0 then table.remove(f)end elseif g~="."and g~=""then table.insert(f,g)end end;return"/"..table.concat(f,"/")..(e:sub(-1)=="/"and"/"or"")end;local function h(e)local i=a.currentTask;local j=i.cwd or"/"if e:sub(1,1)~="/"then e=j..e end;e=d(e)local k="/"local l="$"for m,n in pairs(b.mounts)do if e:sub(1,#n)==n and#n>#k then k=n;l=m end end;local o=e:sub(#k+1)return c[l],o end;local function p(q,r,s,e)return{disk=q,handle=r,mode=s,path=e,refcount=1}end;local function t(i)local u=0;while i.fd[u]do u=u+1 end;return u end;local function t(i)local v=0;for w in pairs(i.fd)do v=v+1 end;if v>=a.config.maxFilesPerTask then error("EMFILE")end;local u=0;while i.fd[u]do u=u+1 end;return u end;local function x()local y=0;for w,i in pairs(a.tasks or{})do for w in pairs(i.fd)do y=y+1 end end;if y>=a.config.maxOpenFiles then error("ENFILE")end end;function b.open(e,s)local i=a.currentTask;x()local q,o=h(e)if not q then error("No disk mounted for path '"..e.."'")end;local r=q:open(o,s)if not r then return nil end;local z=p(q.address,r,s,e)local u=t(i)i.fd[u]=z;return u end;function b.close(u)local i=a.currentTask;local z=i.fd[u]if not z then error("EBADF")end;i.fd[u]=nil;z.refcount=z.refcount-1;if z.refcount==0 then z.handle.close()end;return true end;function b.read(u,v)local z=a.currentTask.fd[u]if not z then error("EBADF")end;if not z.mode:find("r")then error("File not open for reading")end;return z.handle.read(v)end;function b.write(u,A)local z=a.currentTask.fd[u]if not z then error("EBADF")end;if not z.mode:find("w")then error("File not open for writing")end;return z.handle.write(A)end;function b.whereis(u)local z=a.currentTask.fd[u]if not z then error("EBADF")end;return z.path end;function b.mkdir(e)local q,o=h(e)if not q then error("No disk mounted")end;return q:makeDirectory(o)end;function b.remove(e)local q,o=h(e)if not q then error("No disk mounted")end;return q:remove(o)end;function b.attributes(e)if type(e)=="number"then local z=a.currentTask.fd[e]if not z then error("EBADF")end;return c[z.disk]:attributes(z.path)end;local q,o=h(e)if not q then error("No disk mounted")end;return q:attributes(o)end;function b.list(e)local q,o=h(e)if not q then error("No disk mounted")end;return q:list(o)end;function b.exists(e)local q,o=h(e)if not q then return false end;return q:directoryExists(o)or q:fileExists(o)end;function b.type(e)if type(e)=="number"then local z=a.currentTask.fd[e]if not z then error("EBADF")end;return c[z.disk]:type(z.path)end;local q,o=h(e)if not q then error("No disk mounted")end;return q:type(o)end;function b.getcwd()return a.currentTask.cwd end;function b.setcwd(e)if e:sub(-1)~="/"then e=e.."/"end;if e:sub(1,1)~="/"then e="/"..e end;a.currentTask.cwd=d(e)end;function b.mount(B,e)if a.uid~=0 then error("Permission denied")end;if not c[B]then error("Unknown disk '"..B.."'")end;if e:sub(-1)~="/"then e=e.."/"end;for w,n in pairs(b.mounts)do if n==e then error("Mount point already used")end end;b.mounts[B]=e end;function b.unmount(e)if a.uid~=0 then error("Permission denied")end;for m,n in pairs(b.mounts)do if n==e then b.mounts[m]=nil;return true end end;return false end;function b.getMounts()return b.mounts end;function b.virtdisk(C)a.disks[C.address]=C;a.log("Registered virtual disk at "..C.address)end;function b.dup(D)local i=a.currentTask;local z=i.fd[D]if not z then error("EBADF")end;local E=t(i)i.fd[E]=z;z.refcount=z.refcount+1;return E end;function b.dup2(D,E)local i=a.currentTask;local z=i.fd[D]if not z then error("EBADF")end;if D==E then return E end;if i.fd[E]then b.close(E)end;i.fd[E]=z;z.refcount=z.refcount+1;return E end;a.vfs=b;a.syscalls["VFS_open"]=b.open;a.syscalls["VFS_read"]=b.read;a.syscalls["VFS_write"]=b.write;a.syscalls["VFS_close"]=b.close;a.syscalls["VFS_list"]=b.list;a.syscalls["VFS_type"]=b.type;a.syscalls["VFS_attributes"]=b.attributes;a.syscalls["VFS_mkdir"]=b.mkdir;a.syscalls["VFS_remove"]=b.remove;a.syscalls["VFS_exists"]=b.exists;a.syscalls["VFS_mount"]=b.mount;a.syscalls["VFS_unmount"]=b.unmount;a.syscalls["VFS_getcwd"]=b.getcwd;a.syscalls["VFS_setcwd"]=b.setcwd;a.syscalls["VFS_whereis"]=b.whereis;a.syscalls["VFS_dup"]=b.dup;a.syscalls["VFS_dup2"]=b.dup2;a.log("VFS module loaded")

View File

@@ -1 +0,0 @@
local a=...local b={}local c={"/lib/?.lua","/lib/?","/usr/lib/?.lua","/usr/lib/?","/usr/local/lib/?.lua","/usr/local/lib/?","?.lua","?"}function require(d,...)if b[d]then return b[d]end;local e=d:gsub("%.","/")local f={}for g,h in ipairs(c)do local i=string.gsub(h,"%?",e)if i:sub(1,1)~="/"then i=a.currentTask.cwd..i end;if a.vfs.exists(i)then if a.vfs.type(i)=="directory"then i=i.."/init"end;if a.vfs.exists(i)then local j=a.vfs.open(i,"r")local k=a.vfs.read(j,1024*1024*4)a.vfs.close(j)return assert(load(k,i,"t",a._U))(...)else table.insert(f,i)end else table.insert(f,i)end end;error("Module not found: "..d.." (searched paths: "..table.concat(f,", ")..")")end

View File

@@ -1 +0,0 @@
local a=...local b={}local c={}b.address="devfs0000"b.isReadOnly=false;b.spaceUsed=function()return 0 end;b.spaceTotal=function()return 0 end;b.makeDirectory=function()error("Permission denied")end;b.remove=function()error("Permission denied")end;b.setLabel=function()error("Permission denied")end;b.getLabel=function()return"devfs"end;b.attributes=function(d)return{type=b.type(d),isReadOnly=false,size=0,lastModified=0,created=0,Permissions="666",owner="root",group="root"}end;local function e(d)local f=string.split(d,"/")if f[1]==""then table.remove(f,1)end;local g=c;for h,i in ipairs(f)do if g[i]then g=g[i]else return nil end end;return g end;b.type=function(d)local g=e(d)if g then return g.type else return nil end end;b.list=function(d)local g=e(d)if g and g.type=="directory"then local j=table.keys(g)table.remove(j,table.indexOf(j,"type"))return j else error("Not a directory")end end;b.open=function(d,k)local g=e(d)if g and(g.type=="file"or g.type=="character device")then if k=="r"then return{read=g.read,close=function()end}elseif k=="w"then return{write=g.write,close=function()end}else error("Invalid mode")end else error("Not a file"..type(g))end end;local function l(j)return{type="file",read=function()return j end,write=function(m)j=m end}end;local function n()return{type="directory"}end;c["random"]={type="character device",read=function(o)local p=""for h=1,o do p=p..string.char(math.random(0,255))end;return p end,write=function()error("Permission denied")end}c["null"]={type="character device",read=function()return""end,write=function()end}c["zero"]={type="character device",read=function(o)return string.rep("\0",o)end,write=function()error("Permission denied")end}c["rtc"]={type="character device",read=function()return a.computer:time()end,write=function()error("Permission denied")end}c["rtc0"]={type="character device",read=function()return a.computer:time()end,write=function()error("Permission denied")end}c["eeprom"]={type="character device",read=function()return a.computer:getEEPROM()end,write=function(c)if a.uid~=0 then error("Permission denied")end;a.computer:setEEPROM(c)end}local q=a.newFifo()local r=a.newFifo()c["input"]=n()c["input"]["keyboard"]={type="pipe",read=function(o)return q.pop()end,write=function()error("Permission denied")end}c["input"]["mouse"]={type="pipe",read=function(o)return r.pop()end,write=function()error("Permission denied")end}c["pts"]=n()a.devfs={}a.devfs.keyboard=q;a.devfs.mouse=r;a.devfs.proxy=b;a.devfs.data=c;a.vfs.virtdisk(b)

View File

@@ -1 +0,0 @@
local a=...a.processes.keventd=function()while true do local b={a.computer:getMachineEvent()}if b[1]then if b[1]=="key"or b[1]=="keyPressed"or b[1]=="keyReleased"then a.devfs.keyboard.push(b)end end end end

View File

@@ -1 +0,0 @@
local a=...for b,c in ipairs(string.split(a.fstab,"\n"))do if c:sub(1,1)=="U"then local d=""for b=3,#c do if c:sub(b,b)==";"then if b==3 then a.log("Invalid fstab line... Skipping.","WARN")goto e end;d=c:sub(3,b-1)end end;local f=c:sub(#d+4)if d~="$"then a.vfs.mount(d,f)end::e::end end

View File

@@ -1 +0,0 @@
local a=...local b={}local c={}local d={}function d.open(e)if not b[e]then local f=a.newUUID()b[e]={owner=a.currentProcess.pid,handle=f,messages={}}return b[e].handle end;error("Port already opened")end;function d.close(e)if b[e]then if b[e].owner==a.currentProcess.pid then b[e]=nil;return true else error("Cannot close port you do not own")end end;error("Port not opened")end;function d.send(e,g)if b[e]then table.insert(b[e].messages,{from=a.currentProcess.pid,message=g})if c[b[e].owner]then c[b[e].owner](e,g)end;return true end;error("Port not opened")end;function d.receive(e)if b[e]then if#b[e].messages>0 then return table.remove(b[e].messages,1)else return nil end end;error("Port not opened")end;function d.setSignalHandler(h,i)c[h]=i end;function d.clearSignalHandler(h)c[h]=nil end;function d.sendSignal(h,...)coroutine.resumeWithTimeout(coroutine.create(function(...)if c[h]then c[h](...)return true end end),100)return false end;a.ipc=d;a.syscalls["ipc_open"]=d.open;a.syscalls["ipc_close"]=d.close;a.syscalls["ipc_send"]=d.send;a.syscalls["ipc_receive"]=d.receive;a.syscalls["ipc_setSignalHandler"]=d.setSignalHandler;a.syscalls["ipc_clearSignalHandler"]=d.clearSignalHandler;a.syscalls["ipc_sendSignal"]=d.sendSignal;a.log("Loaded IPC module")

View File

@@ -1 +0,0 @@
local a={...}local b=a[1]b._G=_G;b._U=setmetatable({},{__index=b._G,__newindex=function(c,d,e)if b.config.allowGlobalOverwrites or b.allowGlobalOverwrites then rawset(c,d,e)return end;error("Attempt to modify global variable '"..d.."'",2)end,__metatable=false})b.allowGlobalOverwrites=true;b._U._G=b._U;b.allowGlobalOverwrites=false

View File

@@ -1 +0,0 @@
local a=...local b={}a.pam=b;local c={}local function d(e)local f=a.vfs.open(e,"r")if not f then error("Failed to open file: "..e)end;local g=a.vfs.read(f,1024000)a.vfs.close(f)return g end;local h=require("crypto.blake2s")if not h then error("Failed to load blake2s")end;if not a.vfs.exists("/etc/pam.d/secret")then local i=""for j=1,256 do i=i..string.char(math.random(1,255))end;local k=a.vfs.open("/etc/pam.d/secret","w")a.vfs.write(k,i)a.vfs.close(k)end;local l=d("/etc/pam.d/secret")function b.authenticate(m,n)local o=d("/etc/passwd")local p=d("/etc/shadow")local q=string.split(o,"\n")local r=string.split(p,"\n")local s={}local t={}for u,v in ipairs(q)do local w=string.split(v,":")s[w[1]]=w end;for u,v in ipairs(r)do local w=string.split(v,":")t[w[1]]=w end;for x,w in pairs(s)do if x==m then local y=string.split(t[x][2],"$")local z=y[2]local A=h(n..z,l)if A==y[3]then c[m]=a.newUUID()return c[m]else return false end end end end;function b.authToken(m,B)return c[m]==B end

View File

@@ -1 +0,0 @@
local a=...local b={}local c={}local d=2;a.exitMain=false;function c.spawn(e,f,g,h,i,j)local k=d;d=d+1;b[tostring(k)]={coro=coroutine.create(function()local l,m=xpcall(e,debug.traceback,table.unpack(h or{}))if not l then b[tostring(k)].status="Z"b[tostring(k)].exit=tostring(m)else b[tostring(k)].status="Z"b[tostring(k)].exit=m end end),name=f or"task"..tostring(k),envars=g or{},args=h or{},status="R",pid=k,tgid=i or a.currentTask.tgid,user=a.user,uid=a.uid,fd=j or{},exit="",sleep=0,ivs=0,vs=0,children={},parent=a.currentTask,siblings=a.currentTask.children,syscallReturn={},cwd=a.currentTask.cwd,term=a.currentTask.term,timeSlice=0,lastTime=0,totalTime=0,numRuns=0}table.insert(a.currentTask.children,b[tostring(k)])end;function c.sleep(n)a.currentTask.status="S"a.currentTask.sleep=a.computer:time()+n;coroutine.yield()end;a.syscalls["HPV_spawn"]=c.spawn;a.syscalls["HPV_sleep"]=c.sleep;a._G.sleep=function(...)coroutine.yield("syscall","HPV_sleep",...)end;local function o()for p,q in pairs(b)do if q.status=="Z"and not q.reapTime then q.coro=nil;q.ivs=nil;q.vs=nil;q.args=nil;q.envars=nil;q.cwd=nil;q.term=nil;q.numRuns=nil;q.totalTime=nil;q.lastTime=nil;q.timeSlice=nil;q.syscallReturn=nil;q.sleep=nil;for r,s in pairs(q.fd)do a.vfs.close(s)end;q.fd=nil;q.reapTime=a.computer:time()+30000 elseif q.reapTime and a.computer:time()>q.reapTime then for t,u in ipairs(q.children)do u.parent=b["1"]u.siblings=b["1"].children;table.insert(b["1"].children,u)end;for v,w in ipairs(q.siblings)do if w.pid==q.pid then table.remove(q.siblings,v)break end end;b[p]=nil end end end;local x=0.85;local y=0.01;local z=0.0005;local A=0.5;local B=0.08;local C=0.03;local D=0.02;local E=0.5;local F=0.5;local G=0.01;function a.main()while not a.exitMain do local H=0;local I=0;local J=0;local K=0;local L={}for p,q in pairs(b)do if q.status=="S"then if a.computer:time()>=q.sleep then q.status="R"q.sleep=0 end end;if q.status=="R"then a.currentTask=q;a.vfs.cwd=q.cwd;a.user=q.user;a.uid=q.uid;H=H+1;q.timeSlice=math.min(A,math.max(z,G/H^x))local M=a.computer:time()local N;if a.config.preempt then N={coroutine.resumeWithTimeout(q.coro,q.timeSlice,table.unpack(q.syscallReturn))}else N={coroutine.resume(q.coro,table.unpack(q.syscallReturn))}end;local O=a.computer:time()-M;q.lastTime=O;q.totalTime=(q.totalTime or 0)+O;q.numRuns=(q.numRuns or 0)+1;L[#L+1]=O;K=K+O;if O<=z then I=I+1 end;if O>=A then J=J+1 end;if N[1]=="error"then a.log("processHandlerException: "..N[2])q.status="Z"q.exit="processHandlerException: "..N[2]elseif N[1]=="timeout"then q.ivs=q.ivs+1;q.syscallReturn={}elseif N[1]=="success"then q.vs=q.vs+1;if N[2]=="syscall"then if a.syscalls[N[3]]then if a.config.debugSyscalls then a.log("Task "..q.pid.." invoking syscall: "..N[3],"DBUG")end;local P={xpcall(a.syscalls[N[3]],debug.traceback,table.unpack(N,4))}if a.config.debugSyscalls then if not P[1]then a.log("Task "..q.pid.." syscall "..N[3].." failed: "..tostring(P[2]))else a.log("Task "..q.pid.." syscall "..N[3].." completed returning "..tostring(#P-1).." values","DBUG")for v=2,#P do a.log(" retval["..tostring(v-1).."] = "..tostring(P[v]),"DBUG")end end end;if not P[1]then q.syscallReturn={false,P[2]}else q.syscallReturn={true,table.unpack(P,2)}end else q.syscallReturn={false,"Unknown syscall: "..tostring(N[3])}end end end end end;local Q=H>0 and K/H or 0;local R=0;for t,S in ipairs(L)do R=R+(S-Q)^2 end;if H>0 then R=R/H end;if H>0 then local T=E*I/H-F*J/H;local U=y*H^(x-1)/math.max(Q,1e-8)G=G+B*(U-G)+C*T-D*R end;o()end end;a.tasks=b;a.hpv=c

View File

@@ -1 +0,0 @@
local a=...local debug=debug;a._G.debug={getinfo=debug.getinfo,traceback=debug.traceback}

View File

@@ -1 +0,0 @@
local a=...a.tty={}function a.tty.register(b,c)a.tty[b]=c end;function a.tty.print(d)local e=a.currentTask.term;if e and a.tty[e]then a.tty[e].print(d)end end;function a.tty.printInline(d)local e=a.currentTask.term;if e and a.tty[e]then a.tty[e].printInline(d)end end;function a.tty.size()local e=a.currentTask.term;if e and a.tty[e]then return a.tty[e].size()end end;function a.tty.setCursorPos(f,g)local e=a.currentTask.term;if e and a.tty[e]then return a.tty[e].setCursorPos(f,g)end end;function a.tty.getCursorPos()local e=a.currentTask.term;if e and a.tty[e]then return a.tty[e].getCursorPos()end end;function a.tty.clear()local e=a.currentTask.term;if e and a.tty[e]then return a.tty[e].clear()end end;function a.tty.setTextColor(h)local e=a.currentTask.term;if e and a.tty[e]then return a.tty[e].setTextColor(h)end end;function a.tty.setBackgroundColor(h)local e=a.currentTask.term;if e and a.tty[e]then return a.tty[e].setBackgroundColor(h)end end;function a.tty.scroll(i)local e=a.currentTask.term;if e and a.tty[e]then return a.tty[e].scroll(i)end end;function a.tty.getTextColor()local e=a.currentTask.term;if e and a.tty[e]and a.tty[e].getTextColor then return a.tty[e].getTextColor()end end;function a.tty.getBackgroundColor()local e=a.currentTask.term;if e and a.tty[e]and a.tty[e].getBackgroundColor then return a.tty[e].getBackgroundColor()end end;function a.tty.bind(j)if not j then return false,"No TTY ID specified"end;if not a.tty[j]then return false,"TTY "..tostring(j).." not registered"end;a.currentTask.term=j;return true end;function a.tty.unbind()a.currentTask.term=false end;function a.tty.isBound()return a.currentTask.term~=nil end;function a.tty.getBoundTTY()return a.currentTask.term end;a.syscalls["TTY_print"]=a.tty.print;a.syscalls["TTY_printInline"]=a.tty.printInline;a.syscalls["TTY_size"]=a.tty.size;a.syscalls["TTY_setCursorPos"]=a.tty.setCursorPos;a.syscalls["TTY_getCursorPos"]=a.tty.getCursorPos;a.syscalls["TTY_clear"]=a.tty.clear;a.syscalls["TTY_setTextColor"]=a.tty.setTextColor;a.syscalls["TTY_setBackgroundColor"]=a.tty.setBackgroundColor;a.syscalls["TTY_scroll"]=a.tty.scroll;a.syscalls["TTY_getTextColor"]=a.tty.getTextColor;a.syscalls["TTY_getBackgroundColor"]=a.tty.getBackgroundColor;a.syscalls["TTY_bind"]=a.tty.bind;a.syscalls["TTY_unbind"]=a.tty.unbind;a.syscalls["TTY_isBound"]=a.tty.isBound;a.syscalls["TTY_getBoundTTY"]=a.tty.getBoundTTY;a.log("TTY module loaded attempting to register console tty")a.status="init"

View File

@@ -1 +0,0 @@
local a=...a.tty.bind("tty0")

View File

@@ -1 +0,0 @@
local a=...function print(...)coroutine.yield()local b={...}local c=""for d=1,#b do c=c..tostring(b[d]).."\t"end;c=c:sub(1,-2)a.tty.print(c)end;function printf(e,...)coroutine.yield()local c=string.format(e,...)a.tty.print(c)end;function printInline(...)coroutine.yield()local b={...}local c=""for d=1,#b do c=c..tostring(b[d]).."\t"end;c=c:sub(1,-2)a.tty.printInline(c)end

View File

@@ -1 +0,0 @@
local a=...a.log("Loading init system...")a.log("InitPath: "..a.config.initPath)local b=a.vfs.open(a.config.initPath,"r")local c=a.vfs.read(b,1024*1024*4)a.vfs.close(b)local d,e=load(c,"@sysinit")if not d then error("Failed to load init system: "..e)end;a.tasks["1"]={coro=coroutine.create(function()local f,e=xpcall(d,debug.traceback,a)if not f then a.panic("Init system crashed: "..tostring(e))else a.panic("Init system exited: "..tostring(e))end end),name="sysinit",status="R",pid=1,tgid=1,user="root",uid=0,fd={},exit="",sleep=0,ivs=0,vs=0,parent=a.kernelTask,siblings=a.kernelTask.children,children={},syscallReturn={},cwd="/",timeSlice=0,lastTime=0,totalTime=0,numRuns=0}a.log("created init task with PID 1")a.log("Initializing init system...")

File diff suppressed because one or more lines are too long

View File

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

View File

@@ -1,82 +0,0 @@
local kernel=...
local fs=require("sys.fs")
syscall.TTY_bind("tty0")
local files = fs.list("/bin/startup")
if not files then error("Failed to list /bin/startup") end
for i,v in ipairs(files) do
if v:sub(-4) == ".lua" then
local filepath = "/bin/startup/" .. v
kernel.log("Executing startup script: " .. filepath, "INFO")
local startupFunc, err = load(fs.readAllText(filepath), "@" .. filepath)
if not startupFunc then
kernel.log("Error loading startup script '" .. filepath .. "': " .. err, "ERROR")
else
kernel.hpv.spawn(function()
local status, err = pcall(startupFunc)
if not status then
kernel.log("Error executing startup script '" .. filepath .. "': " .. err, "ERROR")
else
kernel.log("Successfully executed startup script: " .. filepath, "INFO")
end
end, "startup:" .. v)
end
end
end
local function serialize(table, seen)
seen = seen or {}
if seen[tostring(table)] then
return "\"<circular reference>\""
end
seen[tostring(table)] = true
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, seen)
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)
elseif type(v) == "userdata" 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
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
while true do
--kernel.log(serialize(kernel.tasks))
kernel.saveLog()
sleep(1000)
end

20
Makefile Normal file
View File

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

View File

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

View File

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

View File

@@ -1,17 +0,0 @@
print(".. *. ..")
print(" *= +@* +* ")
print(" .@#. -@@@= :#@. ")
print(" =@@+ *@@@# +@@= ")
print(" %@@%: *@@@# -%@@% ")
print(" :@@@@+ *@@@# .*@@@@: ")
print(" :*@@@%- *@@@# -@@@@*: ")
print(" =%@@#. *@@@# .#@@%= ")
print(" :=. :*@@= *@@@# =@@+: .=: ")
print(" %@#=..*# +@@@# #*..=#@# ")
print(" .@@@@+=# .%@%: #=+@@@@. ")
print(" .....=# -@= *+...:. ")
print(" -*%*-@= - =@-*%*- ")
print(" -@*. -@%. :%@- :*@- ")
print(" .#@#@* ")
print(" -#- ")
print(" ")

33
Src/Hyperion-bash/bin/cat Normal file
View 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("")

View File

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

111
Src/Hyperion-bash/bin/chgrp Normal file
View File

@@ -0,0 +1,111 @@
--: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
print(name .. ": cannot change group of '" .. path .. "': " .. tostring(err))
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)

262
Src/Hyperion-bash/bin/chmod Normal file
View File

@@ -0,0 +1,262 @@
--: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
print(name .. ": cannot change permissions of '" .. path .. "': " .. tostring(cerr))
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)

144
Src/Hyperion-bash/bin/chown Normal file
View File

@@ -0,0 +1,144 @@
--: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
print(name .. ": cannot change owner of '" .. path .. "': " .. tostring(err))
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)

View File

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

View File

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

View File

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

309
Src/Hyperion-bash/bin/help Normal file
View File

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

View File

@@ -0,0 +1,104 @@
--: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 = {
-- {label, value} label=nil means print value as-is (userhost / separator)
{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)
-- print logo segment in cyan
c(C_LOGO)
printInline(logo_str)
-- print separator pipe
c(C_LABEL)
printInline("| ")
-- print info segment
local row = info[i]
if row then
if row[1] == nil and i == 1 then
-- 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

998
Src/Hyperion-bash/bin/hysh Normal file
View File

@@ -0,0 +1,998 @@
--:Minify:--
syscall.open("/dev/tty/TTY1","r") --stdin (Device 0)
syscall.open("/dev/tty/TTY1","w") --stdout (Device 1)
syscall.open("/dev/null","w") --stderr (device 2)
local success, errorMsg = xpcall(function()
local fs = require("sys.fs")
syscall.devctl(1,"clear")
syscall.devctl(1,"sfgc",1)
syscall.devctl(1,"spos",1,1)
print("HyperionOS hysh Shell")
local userhost = (syscall.getUsername() or "Unknown").."@"..(syscall.getHostname() or "Unknown")
local commandHistory = {}
local terminate = false
syscall.setEnviron("SHELL","rtbash")
syscall.setEnviron("PATH","/bin/")
syscall.chdir("/")
local oldWD = ""
for i = 1, 16 do
syscall.devctl(1,"sbgc",i); printInline(" ")
end
print("\n")
syscall.sigcatch(function(sig)
if sig == 1 then terminate = true end
end)
local function abspath(p)
if not p or p == "" then return syscall.getcwd() end
if p:sub(1,1) ~= "/" then p = syscall.getcwd().."/"..p end
local parts = {}
for seg in p:gmatch("[^/]+") do
if seg == ".." then if #parts > 0 then table.remove(parts) end
elseif seg ~= "." then table.insert(parts, seg) end
end
return "/"..table.concat(parts, "/")
end
local function basename(p)
p = p:gsub("/$","")
return p:match("([^/]+)$") or p
end
local function dirname(p)
p = p:gsub("/$","")
local d = p:match("^(.*)/[^/]+$")
if not d then return "." end
if d == "" then return "/" end
return d
end
local function readall(fd)
local t = {}
while true do
local ok, chunk = pcall(syscall.read, fd, 65536)
if not ok or not chunk or chunk == "" then break end
t[#t+1] = chunk
end
return table.concat(t)
end
local function readfile(path)
local ok, fd = pcall(syscall.open, path, "r")
if not ok then return nil, fd end
local data = readall(fd)
pcall(syscall.close, fd)
return data
end
local function writefile(path, data)
local ok, fd = pcall(syscall.open, path, "w")
if not ok then return false, fd end
pcall(syscall.write, fd, data)
pcall(syscall.close, fd)
return true
end
local function parseargs(rawargs, flagspec, longflags)
local opts = {}
local args = {}
longflags = longflags or {}
local i = 1
while i <= #rawargs do
local v = rawargs[i]
if v == "--" then
for j = i+1, #rawargs do args[#args+1] = rawargs[j] end
break
elseif v:sub(1,2) == "--" then
local lf = v:sub(3)
if longflags[lf] ~= nil then opts[lf] = true
else opts["_unknown"] = v end
elseif v:sub(1,1) == "-" and #v > 1 then
for k = 2, #v do
local c = v:sub(k,k)
if flagspec:find(c, 1, true) then opts[c] = true
else opts["_unknown"] = "-"..c end
end
else
args[#args+1] = v
end
i = i + 1
end
return opts, args
end
local function eachline(text, fn)
local pos = 1
while pos <= #text do
local nl = text:find("\n", pos, true)
if nl then fn(text:sub(pos, nl-1)); pos = nl+1
else fn(text:sub(pos)); break end
end
end
local function splitlines(text)
local lines = {}
eachline(text, function(l) lines[#lines+1] = l end)
return lines
end
local function copyfile(src, dst)
local data, err = readfile(src)
if not data then return false, err end
local ok, err2 = writefile(dst, data)
if not ok then return false, err2 end
local ok2, stat = pcall(syscall.stat, src)
if ok2 and stat and stat.perms then pcall(syscall.chmod, dst, stat.perms) end
return true
end
local function rmtree(path)
local t = syscall.type(path)
if t == "directory" then
local ok, list = pcall(syscall.listdir, path)
if ok then
for _, e in ipairs(list) do
rmtree(path:gsub("/$","").."/"..e)
end
end
end
if t then pcall(syscall.remove, path) end
end
local function copytree(src, dst)
pcall(syscall.mkdir, dst)
local ok, list = pcall(syscall.listdir, src)
if not ok then return end
for _, e in ipairs(list) do
local s = src:gsub("/$","").."/"..e
local d = dst:gsub("/$","").."/"..e
local t = syscall.type(s)
if t == "directory" then copytree(s, d)
elseif t == "file" then copyfile(s, d)
elseif t == "symlink" then
local ok2, tgt = pcall(syscall.readlink, s)
if ok2 then pcall(syscall.remove, d); pcall(syscall.symlink, tgt, d) end
end
end
end
local builtinCmds = {}
builtinCmds.cd = function(path)
local cwd = syscall.getcwd()
local dirIn = path or ""
if dirIn == "-" then
if oldWD == "" then print("hysh: cd: no previous directory"); return end
print(oldWD); local tmp = oldWD; oldWD = cwd; syscall.chdir(tmp); return
end
local target = abspath(dirIn)
if target:sub(-1) ~= "/" then target = target.."/" end
if not fs.isDir(target) then
print("hysh: cd: "..dirIn..": No such directory"); return
end
oldWD = cwd; syscall.chdir(target)
end
builtinCmds.exit = function(code)
syscall.exit(tonumber(code) or 0)
end
builtinCmds.echo = function(...)
local args = {...}
local n = false
local start = 1
if args[1] == "-n" then n = true; start = 2 end
local out = table.concat(args, " ", start)
if n then printInline(out) else print(out) end
end
builtinCmds.pwd = function()
print(syscall.getcwd())
end
builtinCmds["true"] = function() end
builtinCmds["false"] = function() end
builtinCmds.sleep = function(...)
local args = {...}
if #args == 0 then print("sleep: missing operand"); return end
local total = 0
for _, a in ipairs(args) do
local n, u = a:match("^([%d%.]+)([smhd]?)$")
if not n then print("sleep: invalid time '"..a.."'"); return end
n = tonumber(n)
if u == "m" then n = n * 60
elseif u == "h" then n = n * 3600
elseif u == "d" then n = n * 86400 end
total = total + n
end
sleep(total)
end
builtinCmds.clear = function()
syscall.devctl(1,"clear")
syscall.devctl(1,"sfgc",1)
syscall.devctl(1,"spos",1,1)
end
builtinCmds.whoami = function()
print(syscall.getUsername() or "unknown")
end
builtinCmds.hostname = function(...)
local args = {...}
if #args == 0 then
print(syscall.getHostname() or "unknown")
else
local ok, err = pcall(syscall.setHostname, args[1])
if not ok then print("hostname: "..tostring(err)) end
end
end
builtinCmds.uname = function(...)
local opts, _ = parseargs({...}, "asnrm")
local all = opts.a
local parts = {}
local results = {}
for word in string.gmatch(syscall.version(), "%S+") do
table.insert(results, word)
end
if all or opts.s or (not opts.s and not opts.n and not opts.r and not opts.m) then
parts[#parts+1] = results[1]
end
if all or opts.n then parts[#parts+1] = (syscall.getHostname() or "hyperion") end
if all or opts.r then parts[#parts+1] = results[2] end
if all or opts.m then parts[#parts+1] = "virtual" end
print(table.concat(parts, " "))
end
builtinCmds.printenv = function(...)
local args = {...}
local vars = {"PATH","HOME","USER","SHELL","TERM","HOSTNAME","PWD","OLDPWD","LANG","TZ","EDITOR"}
if #args == 0 then
for _, k in ipairs(vars) do
local ok, v = pcall(syscall.getEnviron, k)
if ok and v then print(k.."="..v) end
end
else
for _, k in ipairs(args) do
local ok, v = pcall(syscall.getEnviron, k)
if ok and v then print(v) end
end
end
end
builtinCmds.env = function(...)
local args = {...}
local i = 1
while i <= #args and args[i]:match("^[%w_]+=") do
local k, v = args[i]:match("^([^=]+)=(.*)")
pcall(syscall.setEnviron, k, v)
i = i + 1
end
if i > #args then
builtinCmds.printenv()
end
end
builtinCmds.touch = function(...)
local _, args = parseargs({...}, "amc")
if #args == 0 then print("touch: missing operand"); return end
for _, a in ipairs(args) do
local path = abspath(a)
if not syscall.exists(path) then
local ok, fd = pcall(syscall.open, path, "w")
if ok then pcall(syscall.close, fd)
else print("touch: cannot create '"..a.."': "..tostring(fd)) end
end
end
end
builtinCmds.mkdir = function(...)
local opts, args = parseargs({...}, "p")
if #args == 0 then print("mkdir: missing operand"); return end
for _, a in ipairs(args) do
local path = abspath(a)
if path:sub(-1) ~= "/" then path = path.."/" end
if fs.isDir(path) then
print("mkdir: cannot create '"..a.."': Directory exists"); return
end
local ok, err = pcall(syscall.mkdir, path)
if not ok then print("mkdir: cannot create '"..a.."': "..tostring(err)) end
end
end
builtinCmds.rm = function(...)
local opts, args = parseargs({...}, "rRf")
if #args == 0 then print("rm: missing operand"); return end
local recursive = opts.r or opts.R
for _, a in ipairs(args) do
local path = abspath(a)
local t = syscall.type(path)
if not t then
if not opts.f then print("rm: cannot remove '"..a.."': No such file or directory") end
elseif t == "directory" then
if not recursive then print("rm: cannot remove '"..a.."': Is a directory")
else rmtree(path) end
else
local ok, err = pcall(syscall.remove, path)
if not ok then print("rm: cannot remove '"..a.."': "..tostring(err)) end
end
end
end
builtinCmds.cp = function(...)
local opts, args = parseargs({...}, "rRp")
if #args < 2 then print("cp: missing operand"); return end
local recursive = opts.r or opts.R
local dst = abspath(args[#args])
local dstIsDir = syscall.type(dst) == "directory"
if #args > 2 and not dstIsDir then
print("cp: target '"..args[#args].."' is not a directory"); return
end
for i = 1, #args-1 do
local src = abspath(args[i])
local t = syscall.type(src)
if not t then
print("cp: '"..args[i].."': No such file or directory")
elseif t == "directory" then
if not recursive then print("cp: omitting directory '"..args[i].."'")
else copytree(src, dstIsDir and (dst:gsub("/$","").."/"..basename(args[i])) or dst) end
else
local d = dstIsDir and (dst:gsub("/$","").."/"..basename(args[i])) or dst
local ok, err = copyfile(src, d)
if not ok then print("cp: '"..args[i].."': "..tostring(err)) end
end
end
end
builtinCmds.mv = function(...)
local opts, args = parseargs({...}, "f")
if #args < 2 then print("mv: missing operand"); return end
local dst = abspath(args[#args])
local dstIsDir = syscall.type(dst) == "directory"
if #args > 2 and not dstIsDir then
print("mv: target '"..args[#args].."' is not a directory"); return
end
for i = 1, #args-1 do
local src = abspath(args[i])
local t = syscall.type(src)
if not t then
print("mv: '"..args[i].."': No such file or directory")
else
local d = dstIsDir and (dst:gsub("/$","").."/"..basename(args[i])) or dst
if t == "directory" then
copytree(src, d); rmtree(src)
else
local ok, err = copyfile(src, d)
if ok then pcall(syscall.remove, src)
else print("mv: '"..args[i].."': "..tostring(err)) end
end
end
end
end
builtinCmds.cat = function(...)
local args = {...}
if #args == 0 then
while true do
local ok, chunk = pcall(syscall.read, 0, 4096)
if not ok or not chunk or chunk == "" then break end
printInline(chunk)
end
print("")
return
end
for _, a in ipairs(args) do
local path = abspath(a)
if not syscall.exists(path) then
print("cat: "..a..": No such file or directory")
else
local ok, fd = pcall(syscall.open, path, "r")
if not ok then print("cat: "..a..": "..tostring(fd))
else
while true do
local ok2, chunk = pcall(syscall.read, fd, 65536)
if not ok2 or not chunk or chunk == "" then break end
printInline(chunk)
end
pcall(syscall.close, fd)
end
end
end
end
builtinCmds.head = function(...)
local raw = {...}
local n = 10
local files = {}
local i = 1
while i <= #raw do
local v = raw[i]
if v == "-n" then i=i+1; n=tonumber(raw[i]) or 10
elseif v:match("^%-n%d+$") then n=tonumber(v:sub(3))
elseif v:match("^%-%d+$") then n=tonumber(v:sub(2))
elseif v:sub(1,1) ~= "-" or v == "-" then files[#files+1] = v
end
i=i+1
end
local multi = #files > 1
local function dohead(text, label)
if multi then
syscall.devctl(1,"sfgc",4); print("==> "..label.." <=="); syscall.devctl(1,"sfgc",1)
end
local count = 0
for line in (text.."\n"):gmatch("([^\n]*)\n") do
if count >= n then break end
print(line); count = count+1
end
end
if #files == 0 then
dohead(readall(0), "stdin")
else
for _, a in ipairs(files) do
if a == "-" then dohead(readall(0), "stdin")
else
local data, err = readfile(abspath(a))
if not data then print("head: "..a..": "..tostring(err))
else dohead(data, a) end
end
end
end
end
builtinCmds.tail = function(...)
local raw = {...}
local n = 10
local files = {}
local i = 1
while i <= #raw do
local v = raw[i]
if v == "-n" then i=i+1; n=tonumber(raw[i]) or 10
elseif v:match("^%-n%d+$") then n=tonumber(v:sub(3))
elseif v:match("^%-%d+$") then n=tonumber(v:sub(2))
elseif v:sub(1,1) ~= "-" or v == "-" then files[#files+1] = v
end
i=i+1
end
local multi = #files > 1
local function dotail(text, label)
if multi then
syscall.devctl(1,"sfgc",4); print("==> "..label.." <=="); syscall.devctl(1,"sfgc",1)
end
local lines = splitlines(text)
local start = math.max(1, #lines - n + 1)
for j = start, #lines do print(lines[j]) end
end
if #files == 0 then dotail(readall(0), "stdin")
else
for _, a in ipairs(files) do
if a == "-" then dotail(readall(0), "stdin")
else
local data, err = readfile(abspath(a))
if not data then print("tail: "..a..": "..tostring(err))
else dotail(data, a) end
end
end
end
end
builtinCmds.wc = function(...)
local opts, args = parseargs({...}, "lwc")
local showAll = not opts.l and not opts.w and not opts.c
if showAll then opts.l=true; opts.w=true; opts.c=true end
local function count(text)
local l,w,b = 0,0,#text
for _ in text:gmatch("\n") do l=l+1 end
for _ in text:gmatch("%S+") do w=w+1 end
return l,w,b
end
local function fmt(l,w,c,lbl)
local p={}
if opts.l then p[#p+1]=string.format("%7d",l) end
if opts.w then p[#p+1]=string.format("%7d",w) end
if opts.c then p[#p+1]=string.format("%7d",c) end
if lbl then p[#p+1]=" "..lbl end
print(table.concat(p))
end
local tl,tw,tc = 0,0,0
if #args == 0 then
local l,w,c = count(readall(0)); fmt(l,w,c)
else
for _, a in ipairs(args) do
local data, err = readfile(abspath(a))
if not data then print("wc: "..a..": "..tostring(err))
else
local l,w,c = count(data); fmt(l,w,c,a)
tl=tl+l; tw=tw+w; tc=tc+c
end
end
if #args > 1 then fmt(tl,tw,tc,"total") end
end
end
builtinCmds.grep = function(...)
local opts, args = parseargs({...}, "ivnlcrR", {["ignore-case"]=true,["invert-match"]=true})
if #args == 0 then print("grep: missing pattern"); return end
local pat = args[1]
if opts.i or opts["ignore-case"] then pat = pat:lower() end
local recursive = opts.r or opts.R
local found = false
local function greptext(text, label, showlabel)
local count = 0
local linenum = 0
local hasMatch = false
eachline(text, function(line)
linenum = linenum+1
local test = (opts.i or opts["ignore-case"]) and line:lower() or line
local m = (test:find(pat) ~= nil)
if opts.v or opts["invert-match"] then m = not m end
if m then
count=count+1; hasMatch=true; found=true
if not opts.l and not opts.c then
local out = ""
if showlabel then out=out..label..":" end
if opts.n then out=out..linenum..":" end
print(out..line)
end
end
end)
if opts.l and hasMatch then print(label) end
if opts.c then
print((showlabel and label..":" or "")..count)
end
end
local function grepfile(path, label, showlabel)
local data, err = readfile(path)
if not data then print("grep: "..label..": "..tostring(err)); return end
greptext(data, label, showlabel)
end
local function grepdir(dir, prefix)
local ok, list = pcall(syscall.listdir, dir)
if not ok then return end
for _, e in ipairs(list) do
local fp = dir:gsub("/$","").."/"..e
local lbl = (prefix ~= "" and prefix.."/" or "")..e
local t = syscall.type(fp)
if t == "directory" then grepdir(fp, lbl)
elseif t == "file" then grepfile(fp, lbl, true) end
end
end
if #args == 1 then
greptext(readall(0), "(stdin)", false)
else
local showlabel = #args > 2 or recursive
for i = 2, #args do
local path = abspath(args[i])
local t = syscall.type(path)
if t == "directory" then
if recursive then grepdir(path, args[i])
else print("grep: "..args[i]..": Is a directory") end
elseif t then
grepfile(path, args[i], showlabel)
else
print("grep: "..args[i]..": No such file or directory")
end
end
end
end
builtinCmds.sort = function(...)
local opts, args = parseargs({...}, "rnu")
local lines = {}
local function addlines(text)
local ls = splitlines(text)
for _, l in ipairs(ls) do lines[#lines+1] = l end
end
if #args == 0 then addlines(readall(0))
else
for _, a in ipairs(args) do
local data, err = readfile(abspath(a))
if not data then print("sort: "..a..": "..tostring(err))
else addlines(data) end
end
end
table.sort(lines, function(a,b)
if opts.n then
local na = tonumber(a:match("^%-?%d+%.?%d*")) or 0
local nb = tonumber(b:match("^%-?%d+%.?%d*")) or 0
return opts.r and na > nb or na < nb
end
return opts.r and a > b or a < b
end)
local prev = nil
for _, l in ipairs(lines) do
if not opts.u or l ~= prev then print(l); prev = l end
end
end
builtinCmds.uniq = function(...)
local opts, args = parseargs({...}, "cdui")
local text
if args[1] then
local data, err = readfile(abspath(args[1]))
if not data then print("uniq: "..args[1]..": "..tostring(err)); return end
text = data
else text = readall(0) end
local prev, prevCount = nil, 0
local out = {}
local function emit(line, count)
if opts.d and count == 1 then return end
if opts.u and count > 1 then return end
out[#out+1] = opts.c and string.format("%7d %s", count, line) or line
end
eachline(text, function(line)
local cmp = opts.i and line:lower() or line
local cprev = opts.i and (prev and prev:lower()) or prev
if cmp == cprev then prevCount = prevCount+1
else
if prev ~= nil then emit(prev, prevCount) end
prev = line; prevCount = 1
end
end)
if prev ~= nil then emit(prev, prevCount) end
if args[2] then
local ok, err = writefile(abspath(args[2]), table.concat(out, "\n").."\n")
if not ok then print("uniq: "..args[2]..": "..tostring(err)) end
else
for _, l in ipairs(out) do print(l) end
end
end
builtinCmds.tee = function(...)
local opts, args = parseargs({...}, "a")
local mode = opts.a and "a" or "w"
local fds = {}
for _, a in ipairs(args) do
local ok, fd = pcall(syscall.open, abspath(a), mode)
if not ok then print("tee: "..a..": "..tostring(fd))
else fds[#fds+1] = fd end
end
while true do
local ok, chunk = pcall(syscall.read, 0, 4096)
if not ok or not chunk or chunk == "" then break end
pcall(syscall.write, 1, chunk)
for _, fd in ipairs(fds) do pcall(syscall.write, fd, chunk) end
end
for _, fd in ipairs(fds) do pcall(syscall.close, fd) end
end
builtinCmds.find = function(...)
local raw = {...}
local roots, filters = {}, {}
local maxdepth, mindepth = nil, 0
local i = 1
while i <= #raw and raw[i]:sub(1,1) ~= "-" do
roots[#roots+1] = raw[i]; i=i+1
end
if #roots == 0 then roots = {"."} end
while i <= #raw do
local tok = raw[i]
if tok == "-name" then
i=i+1
local pat = "^"..(raw[i] or ""):gsub("([%.%+%-%^%$%(%)%[%]%%])","%%%1"):gsub("%*",".*"):gsub("%?",".").. "$"
filters[#filters+1] = function(_, e, _2, _3) return e:match(pat) ~= nil end
elseif tok == "-type" then
i=i+1; local ft=raw[i] or ""
filters[#filters+1] = function(_, _, t, _2)
if ft=="f" then return t=="file"
elseif ft=="d" then return t=="directory"
elseif ft=="l" then return t=="symlink" end; return true
end
elseif tok == "-maxdepth" then i=i+1; maxdepth=tonumber(raw[i]) or 0
elseif tok == "-mindepth" then i=i+1; mindepth=tonumber(raw[i]) or 0
elseif tok == "-empty" then
filters[#filters+1] = function(path, _, t, _2)
if t=="file" then local ok,s=pcall(syscall.stat,path); return ok and s and (s.size or 0)==0
elseif t=="directory" then local ok,l=pcall(syscall.listdir,path); return ok and l and #l==0 end
return false
end
end
i=i+1
end
local function match(path, e, t, depth)
for _, f in ipairs(filters) do if not f(path,e,t,depth) then return false end end
return true
end
local function walk(path, disp, depth)
local t = syscall.type(path)
if not t then return end
local e = path:match("([^/]+)/?$") or path
if depth >= mindepth and match(path,e,t,depth) then print(disp) end
if t=="directory" and (maxdepth==nil or depth<maxdepth) then
local ok,list = pcall(syscall.listdir, path)
if ok then
table.sort(list)
for _, child in ipairs(list) do
walk(path:gsub("/$","").."/"..child, disp:gsub("/$","").."/"..child, depth+1)
end
end
end
end
local cwd = syscall.getcwd()
for _, r in ipairs(roots) do
local abs = (r == ".") and cwd or abspath(r)
walk(abs, r, 0)
end
end
builtinCmds.stat = function(...)
local _, args = parseargs({...}, "")
if #args == 0 then print("stat: missing operand"); return end
local function permstr(p)
local function b(n) return math.floor(p/(2^n))%2==1 end
return (b(5) and"r"or"-")..(b(4) and"w"or"-")..(b(9) and"x"or"-")..
(b(3) and"r"or"-")..(b(2) and"w"or"-")..(b(8) and"x"or"-")..
(b(1) and"r"or"-")..(b(0) and"w"or"-")..(b(7) and"x"or"-")
end
local function octperm(p)
local function b(n) return math.floor(p/(2^n))%2 end
local o = b(6)*4096+b(5)*256+b(4)*128+b(9)*64+b(3)*32+b(2)*16+b(8)*8+b(1)*4+b(0)*2+b(7)*1
return string.format("%04o",o)
end
for _, a in ipairs(args) do
local path = abspath(a)
if not syscall.exists(path) then
print("stat: '"..a.."': No such file or directory")
else
local t = syscall.type(path)
local ok, s = pcall(syscall.stat, path)
s = ok and s or {}
local tc = t=="directory" and "d" or (t=="symlink" and "l" or "-")
print(" File: "..a)
print(" Size: "..tostring(s.size or 0).." Type: "..(t or "?"))
print(" Owner: "..tostring(s.owner or 0).." Group: "..tostring(s.group or 0))
print("Access: ("..octperm(s.perms or 0).."/"..tc..permstr(s.perms or 0)..")")
if t == "symlink" then
local ok2, tgt = pcall(syscall.readlink, path)
if ok2 then print(" Link: "..tgt) end
end
print("")
end
end
end
builtinCmds.basename = function(...)
local args = {...}
if #args == 0 then print("basename: missing operand"); return end
local p = args[1]
while #p > 1 and p:sub(-1)=="/" do p=p:sub(1,-2) end
local b = p:match("([^/]+)$") or p
if args[2] and b:sub(-#args[2]) == args[2] then b = b:sub(1, #b-#args[2]) end
print(b)
end
builtinCmds.dirname = function(...)
local args = {...}
if #args == 0 then print("dirname: missing operand"); return end
for _, p in ipairs(args) do
while #p > 1 and p:sub(-1)=="/" do p=p:sub(1,-2) end
local d = p:match("^(.*)/[^/]+$")
if not d then print(".")
elseif d == "" then print("/")
else print(d) end
end
end
builtinCmds.df = function(...)
local opts, _ = parseargs({...}, "h")
local function hfmt(n)
local u={"K","M","G","T"}; local i=0
while n>=1024 and i<#u do n=n/1024; i=i+1 end
if i==0 then return tostring(math.floor(n)) end
if n<10 then return string.format("%.1f%s",n,u[i]) end
return math.floor(n)..u[i]
end
local function fmt(n) return opts.h and hfmt(n) or tostring(n) end
print(string.format("%-20s %10s %10s %10s %6s %-s","Filesystem","Size","Used","Avail","Use%","Mounted on"))
local mounts = {{"$","/"}, {"devfs0000","/dev/"}, {"tmpfs0000","/tmp/"}}
for _, m in ipairs(mounts) do
local id, mp = m[1], m[2]
print(string.format("%-20s %10s %10s %10s %6s %-s", id, "?", "?", "?", "?%", mp))
end
end
local function getUserInput()
syscall.devctl(1,"sfgc",3)
syscall.write(1, userhost)
syscall.devctl(1,"sfgc",1)
syscall.write(1, ":")
syscall.devctl(1,"sfgc",10)
syscall.write(1, syscall.getcwd())
syscall.devctl(1,"sfgc",1)
syscall.write(1, "$ ")
local curOffsetStr = syscall.devctl(1, "gpos")
local curOffsetX = tonumber(curOffsetStr:sub(1, curOffsetStr:find(";")-1))
local curOffsetY = tonumber(curOffsetStr:sub(curOffsetStr:find(";")+1))
local input = ""
local blinkState = false
local cursorPos = 1
local history = 0
local dirty = true
local function redraw()
syscall.devctl(1,"spos",curOffsetX,curOffsetY)
syscall.write(1, string.sub(input, 1, cursorPos-1))
if blinkState then
syscall.devctl(1,"sfgc",16); syscall.devctl(1,"sbgc",1)
end
if cursorPos > #input then
syscall.write(1, " ")
else
syscall.write(1, string.sub(input, cursorPos, cursorPos))
end
syscall.devctl(1,"sfgc",1); syscall.devctl(1,"sbgc",16)
syscall.write(1, string.sub(input, cursorPos+1) .. " ")
end
while true do
local key = syscall.read(0)
if key and key ~= "" then
if key=="\19" then if cursorPos>1 then cursorPos=cursorPos-1;dirty=true end
elseif key=="\20" then if cursorPos<=#input then cursorPos=cursorPos+1;dirty=true end
elseif key=="\17" then
if history<#commandHistory then
history=history+1
input=commandHistory[#commandHistory-history+1]
cursorPos=#input+1;dirty=true
end
elseif key=="\18" then
if history>1 then
history=history-1
input=commandHistory[#commandHistory-history+1]
cursorPos=#input+1;dirty=true
elseif history==1 then
history=0;input="";cursorPos=1;dirty=true
end
elseif key=="\b" then
if cursorPos>1 then
input=string.sub(input,1,cursorPos-2)..string.sub(input,cursorPos)
cursorPos=cursorPos-1;dirty=true
end
elseif key=="\n" then
syscall.devctl(1,"sfgc",1);syscall.devctl(1,"sbgc",16)
syscall.devctl(1,"spos",curOffsetX,curOffsetY)
syscall.write(1, input.." \n")
return input
else
input=string.sub(input,1,cursorPos-1)..key..string.sub(input,cursorPos)
cursorPos=cursorPos+1;dirty=true
end
end
local curBlink = ((math.floor(syscall.getUptime()/500)%2)==0)
if curBlink~=blinkState then blinkState=curBlink;dirty=true end
if dirty then redraw();dirty=false end
end
end
local function printError(progName, msg)
syscall.devctl(1,"sfgc",2)
local s = tostring(msg)
local line, rest = s:match("%]:(%d+): (.+)$")
if not line then line, rest = s:match(":(%d+): (.+)$") end
if line then
printInline(progName..": error on line "..line..": "); print(rest)
else
print(progName..": "..s)
end
syscall.devctl(1,"sfgc",1)
end
local function runCommand(command)
do
local func = load("return "..command, "@equation", "t", {})
if func then
local ok, result = pcall(func)
if ok and type(result)=="number" then print(result); return end
end
end
terminate = false
local args = string.split(command, " ")
for i = #args, 1, -1 do
if args[i] == "" then table.remove(args, i) end
end
if #args == 0 then return end
if builtinCmds[args[1]] then
local ok, err = pcall(builtinCmds[args[1]], table.unpack(args, 2))
if not ok then printError(args[1], err) end
return
end
local cmdPath = ""
if string.find(args[1], "/") then
local candidate = args[1]
if candidate:sub(1,1) ~= "/" then candidate = syscall.getcwd().."/"..candidate end
if fs.exists(candidate) then cmdPath = candidate end
else
local paths = string.split(syscall.getEnviron("PATH"), ":")
for _, p in pairs(paths) do
if fs.exists(p..args[1]) then cmdPath = p..args[1]; break end
end
if cmdPath == "" then
local cwd = syscall.getcwd()
local candidate = cwd..(cwd:sub(-1)=="/" and "" or "/")..args[1]
if fs.exists(candidate) then cmdPath = candidate end
end
end
if cmdPath == "" then print(args[1]..": Command not found"); return end
local progName = cmdPath:match("([^/]+)$") or args[1]
local xok, xerr = pcall(syscall.access, cmdPath, "x")
if not xok then
syscall.devctl(1,"sfgc",2); print(progName..": Permission denied"); syscall.devctl(1,"sfgc",1)
return
end
local text = fs.readAllText(cmdPath)
local program, err = load(text, progName)
if not program then
syscall.devctl(1,"sfgc",2)
local line, rest = tostring(err):match(":(%d+): (.+)$")
if line then printInline(progName..": load error on line "..line..": "); print(rest)
else print(progName..": load error: "..tostring(err)) end
syscall.devctl(1,"sfgc",1); return
end
local proc = syscall.spawn(function(...)
syscall.open("/dev/tty/TTY1","r")
syscall.open("/dev/tty/TTY1","w")
syscall.open("/dev/null","w")
local ok2, msg = pcall(program, ...)
if not ok2 then printError(progName, msg) end
end, progName, nil, {table.unpack(args, 2)})
while true do
local exited, code = syscall.collect(proc)
if exited then
if code then print("\nTask exited with code:\n"..tostring(code)) end
return
end
if terminate then
local ok2 = syscall.kill(proc)
if ok2 then
syscall.devctl(1,"sbgc",16); syscall.devctl(1,"sfgc",2)
print("\nProgram Terminated."); syscall.devctl(1,"sfgc",1)
end
terminate = false; break
end
sleep(0.05)
end
end
while true do
local command = getUserInput()
if command ~= "" then
if command ~= commandHistory[#commandHistory] then
table.insert(commandHistory, command)
end
runCommand(command)
end
end
end, debug.traceback)
if not success then
syscall.log("Error running shell: "..errorMsg, "ERROR")
syscall.devctl(1,"sfgc",2)
syscall.devctl(1,"sbgc",16)
print()
print("Error running shell: ")
print(errorMsg)
end

View 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
View File

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

96
Src/Hyperion-bash/bin/ln Normal file
View File

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

166
Src/Hyperion-bash/bin/login Normal file
View File

@@ -0,0 +1,166 @@
--: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)
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

View File

@@ -0,0 +1,157 @@
--:Minify:--
-- Usage:
-- loimgcreate <srcdir> <image.hfs> create image from directory
-- loimgcreate -x <image.hfs> <dest> extract image back to a directory
-- loimgcreate --help
local name = syscall.getTask(syscall.getpid()).name
local args, opts = {}, { x=false, help=false }
for _, v in ipairs({...}) do
if v:sub(1,2) == "--" then
local o = v:sub(3)
if o == "help" then opts.help = true
else print(name..": unrecognised option '"..v.."'")
print("try '"..name.." --help' for more information.")
syscall.exit(1); return end
elseif v:sub(1,1) == "-" then
for i = 2, #v do
local c = v:sub(i,i)
if opts[c] ~= nil then opts[c] = true
else print(name..": invalid option '-"..c.."'")
print("try '"..name.." --help' for more information.")
syscall.exit(1); return end
end
else
table.insert(args, v)
end
end
if opts.help then
print("Usage: "..name.." <srcdir> <image.hfs>")
print(" "..name.." -x <image.hfs> <destdir>")
print("")
print("Pack a directory into a portable HFS image file, or extract one.")
print("")
print(" <srcdir> <image.hfs> recursively pack srcdir into image.hfs")
print(" -x <image.hfs> <dest> extract image.hfs into dest (created if needed)")
print("")
print("HFS images can be mounted with:")
print(" mount -o loop /path/to/image.hfs /mnt/point")
print("")
print("Requires root.")
return
end
local fs = require("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)

View File

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

View File

@@ -0,0 +1,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
View File

@@ -0,0 +1,193 @@
--:Minify:--
local cloptions = {
a = false,
h = false,
l = false,
help = false,
}
local inpArgs = { ... }
local args = {}
local name = syscall.getTask(syscall.getpid()).name
for _, v in pairs(inpArgs) do
if v:sub(1, 2) == "--" then
local opt = v:sub(3)
if cloptions[opt] == nil then
print(name .. ": unrecognized option '" .. v .. "'.")
print("try '" .. name .. " --help' for more information.")
return
end
cloptions[opt] = true
elseif v:sub(1, 1) == "-" then
for i = 2, #v do
local opt = v:sub(i, i)
if cloptions[opt] == nil then
print(name .. ": invalid option '-" .. opt .. "'.")
print("try '" .. name .. " --help' for more information.")
return
end
cloptions[opt] = true
end
else
table.insert(args, v)
end
end
if cloptions.help then
print("Usage: " .. name .. " [OPTION]... [DIR]")
print("List all entries in the specified DIRectory, or cwd if not specified.")
print("")
print("Options:")
print(" -a do not ignore entries starting with .")
print(" -h with -l, print sizes in human readable format")
print(" -l use a long listing format")
print(" --help display this help and exit")
return
end
local fs = require("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

View File

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

325
Src/Hyperion-bash/bin/lua Normal file
View 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

View 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
View 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 ""))

View 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
View File

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

View File

@@ -0,0 +1,80 @@
--:Minify:--
-- passwd: change a user's password
-- Usage: passwd [username] (default: current user)
local args = {...}
local targetName = args[1]
local currentUid = syscall.getuid()
local targetUid
if targetName then
targetUid = syscall.getuid(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
View 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

View File

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

View File

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

429
Src/Hyperion-bash/bin/sed Normal file
View File

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

View File

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

56
Src/Hyperion-bash/bin/su Normal file
View 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
View 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.getuid(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 " .. currentUser .. ": ")
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(currentUser, pw)
if not ok then
sleep(1)
print("sudo: Authentication failure")
syscall.exit(1)
return
end
if targetUid ~= 0 then
syscall.setuid(targetUid)
end
else
if targetUid ~= currentUid then
syscall.setuid(targetUid)
end
end
local cmdPath = ""
if cmd:find("/") then
if fs.exists(cmd) then cmdPath = cmd end
else
local paths = string.split(syscall.getEnviron("PATH") or "/bin/", ":")
for _, p in ipairs(paths) do
local full = p .. cmd
if fs.exists(full) then cmdPath = full; break end
end
end
if cmdPath == "" then
print("sudo: command not found: " .. cmd)
syscall.exit(1)
return
end
local text = fs.readAllText(cmdPath)
local program, loadErr = load(text, "@" .. cmdPath)
if not program then
print("sudo: cannot load " .. cmd .. ": " .. tostring(loadErr))
syscall.exit(1)
return
end
local pwent = syscall.getpasswd(targetUid)
if pwent and pwent.homedir then
syscall.setEnviron("HOME", pwent.homedir)
end
syscall.setEnviron("USER", targetUser)
local ok, err = xpcall(program, debug.traceback, table.unpack(restArgs))
if not ok then
print("sudo: " .. cmd .. ": " .. tostring(err))
syscall.exit(1)
end

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,49 @@
--:Minify:--
local args = {...}
local removeHome = false
local username = nil
for _, a in ipairs(args) do
if a == "-r" then removeHome = true
elseif a:sub(1,1) ~= "-" then username = a
else print("userdel: unknown option: " .. a); syscall.exit(1); return end
end
if not username then
print("Usage: userdel [-r] <username>")
syscall.exit(1); return
end
local uid = syscall.getuid(username)
if not uid then
print("userdel: user '" .. username .. "' does not exist")
syscall.exit(1); return
end
local pwent = syscall.getpasswd(uid)
local ok, err = syscall.deleteuser(uid)
if not ok then
print("userdel: " .. tostring(err))
syscall.exit(1); return
end
if removeHome and pwent and pwent.homedir then
local fs = require("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 .. "'")

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,71 +0,0 @@
local term = {}
function term.clear()
coroutine.yield("VFS_write", 1, "\27C\25")
end
function term.setCursorPos(x, y)
coroutine.yield("VFS_write", 1, "\27cs"..tostring(y)..";"..tostring(x).."\25")
end
function term.size()
coroutine.yield("VFS_write", 1, "\27ts\25")
local ok, data = coroutine.yield("VFS_read", 0, 16) -- read response
if not ok then error("Failed to get terminal size") end
local x, y = string.match(data, "%R(%d+);(%d+)\25")
return tonumber(x), tonumber(y)
end
function term.getCursorPos()
coroutine.yield("VFS_write", 1, "\27gc\25")
local ok, data = coroutine.yield("VFS_read", 0, 16) -- read response
if not ok then error("Failed to get cursor position") end
local y, x = string.match(data, "%R(%d+);(%d+)\25")
return tonumber(x), tonumber(y)
end
function term.write(data)
coroutine.yield("VFS_write", 1, data)
end
function term.setTextColor(color)
local ok, err = coroutine.yield("VFS_type", 1)
if not ok then error(err) end
if ok ~= "tty" then return end
coroutine.yield("VFS_write", 1, "\27f"..tostring(color).."\25")
end
function term.setBackgroundColor(color)
local ok, err = coroutine.yield("VFS_type", 1)
if not ok then error(err) end
if ok ~= "tty" then return end
coroutine.yield("VFS_write", 1, "\27b"..tostring(color).."\25")
end
function term.isColor()
local ok, err = coroutine.yield("VFS_type", 1)
if not ok then error(err) end
return ok == "tty"
end
function term.scroll(n)
coroutine.yield("VFS_write", 1, "\27S"..tostring(n).."\25")
end
function term.setDefault(color, layer)
if layer then
coroutine.yield("VFS_write", 1, "\27F"..tostring(color).."\25")
else
coroutine.yield("VFS_write", 1, "\27B"..tostring(color).."\25")
end
end
function term.showCursor(show)
if show then
coroutine.yield("VFS_write", 1, "\27sc\25")
else
coroutine.yield("VFS_write", 1, "\27hc\25")
end
end
return term

View File

@@ -1,82 +0,0 @@
local kernel=...
local fs=require("sys.fs")
syscall.TTY_bind("tty0")
local files = fs.list("/bin/startup")
if not files then error("Failed to list /bin/startup") end
for i,v in ipairs(files) do
if v:sub(-4) == ".lua" then
local filepath = "/bin/startup/" .. v
kernel.log("Executing startup script: " .. filepath, "INFO")
local startupFunc, err = load(fs.readAllText(filepath), "@" .. filepath)
if not startupFunc then
kernel.log("Error loading startup script '" .. filepath .. "': " .. err, "ERROR")
else
kernel.hpv.spawn(function()
local status, err = pcall(startupFunc)
if not status then
kernel.log("Error executing startup script '" .. filepath .. "': " .. err, "ERROR")
else
kernel.log("Successfully executed startup script: " .. filepath, "INFO")
end
end, "startup:" .. v)
end
end
end
local function serialize(table, seen)
seen = seen or {}
if seen[tostring(table)] then
return "\"<circular reference>\""
end
seen[tostring(table)] = true
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, seen)
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)
elseif type(v) == "userdata" 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
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
while true do
--kernel.log(serialize(kernel.tasks))
kernel.saveLog()
sleep(1000)
end

View File

@@ -1,6 +1,4 @@
-- bit32.lua
-- Full pure-Lua implementation of Lua 5.2 bit32 library
-- NO Lua 5.3 operators used
--:Minify:--
local bit32 = {}
@@ -136,16 +134,4 @@ function bit32.replace(x, v, field, width)
return bit32.bor(x, bit32.lshift(v, field))
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

View File

@@ -1,3 +1,4 @@
--:Minify:--
-- blake2s.lua
-- Pure Lua 5.2, 32-bit only, supports keyed hashing

6
Src/Hyperion-core/lib/io Normal file
View File

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

View File

@@ -1,32 +1,32 @@
--:Minify:--
local fs={}
-- "VFS_open" : open
-- "VFS_read" : read
-- "VFS_write" : write
-- "VFS_close" : close
-- "open" : open
-- "read" : read
-- "write" : write
-- "close" : close
function fs.open(path, mode)
local fd=syscall.VFS_open(path,mode)
local fd=syscall.open(path,mode)
local ret={
close=function()
-- close file
return syscall.VFS_close(fd)
return syscall.close(fd)
end,
flush=function()
-- close and reopen file to flush buffers
syscall.VFS_close(fd)
fd=syscall.VFS_open(path,mode)
syscall.fsync(fd)
end
}
if mode=="r" then
ret.read=function(count)
local data = syscall.VFS_read(fd,count)
local data = syscall.read(fd,count)
return data
end
ret.readAll=function(chunkSize)
local chunks={} -- to store read chunks
while true do
local chunk=syscall.VFS_read(fd,chunkSize or 65536)
local chunk=syscall.read(fd,chunkSize or 65536)
if chunk==nil or #chunk==0 then break end
table.insert(chunks,chunk)
end
@@ -59,7 +59,7 @@ function fs.open(path, mode)
end
-- 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
eof = true
else
@@ -70,12 +70,12 @@ function fs.open(path, mode)
elseif mode=="w" then
ret.write=function(data)
-- write data to file
return syscall.VFS_write(fd,data)
return syscall.write(fd,data)
end
elseif mode=="a" then
ret.write=function(data)
-- append data to file
return syscall.VFS_write(fd,data)
return syscall.write(fd,data)
end
else
error("Invalid mode '"..mode.."'",2)
@@ -105,35 +105,39 @@ function fs.appendAllText(path, data)
end
function fs.mkdir(path)
return syscall.VFS_mkdir(path)
return syscall.mkdir(path)
end
function fs.remove(path)
return syscall.VFS_remove(path)
return syscall.remove(path)
end
function fs.list(path)
return syscall.VFS_list(path)
return syscall.listdir(path)
end
function fs.type(path)
return syscall.VFS_type(path)
return syscall.type(path)
end
function fs.attributes(path)
return syscall.VFS_attributes(path)
function fs.stat(path)
return syscall.stat(path)
end
function fs.exists(path)
return syscall.VFS_exists(path)
return syscall.exists(path)
end
function fs.getcwd()
return syscall.VFS_getcwd()
return syscall.getcwd()
end
function fs.setcwd(path)
return syscall.VFS_setcwd(path)
function fs.chdir(path)
return syscall.chdir(path)
end
function fs.isDir(path)
return syscall.type(path) == "directory"
end
return fs

View File

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

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