Compare commits
70 Commits
6e363a688e
...
1.2.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 6694711423 | |||
| 40c97ca000 | |||
| dd2437d4af | |||
| d026cfbb03 | |||
| aad7efd055 | |||
| 93c3bab263 | |||
| 5bd16b8fd4 | |||
| 3cc1459769 | |||
| 0655f2a39e | |||
| 57b1d46837 | |||
| 875759022a | |||
|
|
bb14ea34c3 | ||
|
|
149fb18564 | ||
| 10bc775e64 | |||
| 287b146621 | |||
|
|
371954373e | ||
|
|
ecef2c6cb0 | ||
|
|
6770533581 | ||
| 19a9c72c6d | |||
| fdb18d4ac5 | |||
|
|
303449e13c | ||
| b6d1b9398f | |||
| bb829cdd8e | |||
| 6b9bed5047 | |||
|
|
753c34bffa | ||
| 403178c832 | |||
| 33cd291c21 | |||
| 1c4f48bd65 | |||
| ec5e63898d | |||
| ea2a0e0e94 | |||
| 4f50d90b79 | |||
| bf1dc9da7a | |||
| 1a455a6025 | |||
| c9ac447484 | |||
| cb73f49962 | |||
| d9caf655fb | |||
| 1c3d2c8b48 | |||
| 9bd9cdaba4 | |||
| 72bfce7b08 | |||
| 6e48fcd5b9 | |||
| 2d98ff64ce | |||
| 6a14b87f44 | |||
| 63bcc2df5c | |||
| fd7ee1aa3b | |||
| 1073362007 | |||
| da5ad3b5cb | |||
| e77fbcaf5d | |||
| 111fe764f8 | |||
|
|
83857d7e87 | ||
| 83814311e5 | |||
| 55fdddeff8 | |||
| 1b21c87654 | |||
| df4823940d | |||
| 70532f6e2c | |||
| bd8fe50770 | |||
| e5d6ec9725 | |||
| c620c4f1ba | |||
| 6b48e80157 | |||
| fbbb8b8d65 | |||
|
|
a88bdc9639 | ||
| 96c22f5237 | |||
| 231b0ced48 | |||
| 4100ecf0a1 | |||
|
|
968f4c54d7 | ||
| 70526c76ba | |||
| 3a8be2bdb7 | |||
| efffc8f0d2 | |||
| b48f926053 | |||
| 7d8055a703 | |||
| 1141193fc8 |
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
# Build output
|
||||
/Build/
|
||||
/build/
|
||||
Build/
|
||||
build/
|
||||
|
||||
# VSCodeCounter
|
||||
/.VSCodeCounter/
|
||||
.VSCodeCounter/
|
||||
10
.vscode/settings.json
vendored
Normal file
10
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"Lua.diagnostics.globals": [
|
||||
"isEqualToAny",
|
||||
"isEqualToAll",
|
||||
"syscall",
|
||||
"printf",
|
||||
"printInline",
|
||||
"toHex"
|
||||
]
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
print(".. *. ..")
|
||||
print(" *= +@* +* ")
|
||||
print(" .@#. -@@@= :#@. ")
|
||||
print(" =@@+ *@@@# +@@= ")
|
||||
print(" %@@%: *@@@# -%@@% ")
|
||||
print(" :@@@@+ *@@@# .*@@@@: ")
|
||||
print(" :*@@@%- *@@@# -@@@@*: ")
|
||||
print(" =%@@#. *@@@# .#@@%= ")
|
||||
print(" :=. :*@@= *@@@# =@@+: .=: ")
|
||||
print(" %@#=..*# +@@@# #*..=#@# ")
|
||||
print(" .@@@@+=# .%@%: #=+@@@@. ")
|
||||
print(" .....=# -@= *+...:. ")
|
||||
print(" -*%*-@= - =@-*%*- ")
|
||||
print(" -@*. -@%. :%@- :*@- ")
|
||||
print(" .#@#@* ")
|
||||
print(" -#- ")
|
||||
print(" ")
|
||||
@@ -1 +0,0 @@
|
||||
print("hello from hello.lua")
|
||||
File diff suppressed because one or more lines are too long
@@ -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
|
||||
@@ -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}
|
||||
@@ -1,2 +0,0 @@
|
||||
U $;/
|
||||
U devfs0000;/dev/
|
||||
@@ -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
|
||||
@@ -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")
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
151
Build/lib/bit32
151
Build/lib/bit32
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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")
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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")
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -1 +0,0 @@
|
||||
local a=...local debug=debug;a._G.debug={getinfo=debug.getinfo,traceback=debug.traceback}
|
||||
@@ -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"
|
||||
@@ -1 +0,0 @@
|
||||
local a=...a.tty.bind("tty0")
|
||||
@@ -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
|
||||
@@ -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
@@ -1,57 +0,0 @@
|
||||
local sys = {}
|
||||
local fs = require("sys.fs")
|
||||
|
||||
function sys.spawn(func, name, envars, args)
|
||||
return coroutine.yield(0x10, func, name, envars, args)
|
||||
end
|
||||
|
||||
function sys.spawnFromFile(path, name, envars, args)
|
||||
local data = fs.readAllText(path)
|
||||
if not data then
|
||||
error("File not found: "..path,2)
|
||||
end
|
||||
local func, err = load(data, "@"..path)
|
||||
if not func then
|
||||
error("Error loading file "..path..": "..tostring(err),2)
|
||||
end
|
||||
return coroutine.yield(0x10, func, name, envars, args)
|
||||
end
|
||||
|
||||
function sys.spawnAndWait(func, name, envars, args)
|
||||
local task = coroutine.yield(0x10, func, name, envars, args)
|
||||
local oldsignal = sys.getSignalHandler(17)
|
||||
local exit = false
|
||||
sys.setSignalHandler(17, function()
|
||||
local tasks = sys.getChildrenTasks(task)
|
||||
if not tasks[task] then
|
||||
exit = true
|
||||
end
|
||||
end)
|
||||
while not exit do
|
||||
coroutine.yield()
|
||||
end
|
||||
sys.setSignalHandler(17, oldsignal)
|
||||
return task
|
||||
end
|
||||
|
||||
function sys.spawnFromFileAndWait(path, name, envars, args)
|
||||
local data = fs.readAllText(path)
|
||||
if not data then
|
||||
error("File not found: "..path,2)
|
||||
end
|
||||
local func, err = load(data, "@"..path)
|
||||
if not func then
|
||||
error("Error loading file "..path..": "..tostring(err),2)
|
||||
end
|
||||
return sys.spawnAndWait(func, name, envars, args)
|
||||
end
|
||||
|
||||
function sys.exit(code)
|
||||
return coroutine.yield(0x11, code)
|
||||
end
|
||||
|
||||
function sys.getTaskInfo(task)
|
||||
return coroutine.yield(0x12, task)
|
||||
end
|
||||
|
||||
return sys
|
||||
@@ -1,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
20
Makefile
Normal file
@@ -0,0 +1,20 @@
|
||||
PYTHON := python3
|
||||
ARCH_FLAG := $(if $(ARCH),--arch $(ARCH),)
|
||||
MODE_FLAG := $(if $(DEV),--dev,--release)
|
||||
|
||||
.PHONY: build build-mini build-test build-mini-test clean
|
||||
|
||||
build:
|
||||
$(PYTHON) build.py build $(ARCH_FLAG) $(MODE_FLAG)
|
||||
|
||||
build-mini:
|
||||
$(PYTHON) build.py build-mini $(ARCH_FLAG) $(MODE_FLAG)
|
||||
|
||||
build-test:
|
||||
$(PYTHON) build.py build-test $(ARCH_FLAG) $(MODE_FLAG)
|
||||
|
||||
build-mini-test:
|
||||
$(PYTHON) build.py build-mini-test $(ARCH_FLAG) $(MODE_FLAG)
|
||||
|
||||
clean:
|
||||
$(PYTHON) build.py clean
|
||||
51
README.md
51
README.md
@@ -1,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 don’t break existing functionality.
|
||||
|
||||
Add your name to `contributors.md` when your PR is merged.
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License. See the LICENSE file for details.
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"Name": "Hyperion bash",
|
||||
"Version": "1.0.0",
|
||||
"Publishers": ["HyperionOS Dev Team"],
|
||||
"Description": "basic bash shell",
|
||||
"Dependencies": [
|
||||
"https://git.astronand.dev/Hyperion/HyperionOS/archive/Hyperion-core-v1.0.0.pkg"
|
||||
],
|
||||
"Tar": "https://git.astronand.dev/Hyperion/HyperionOS/archive/Hyperion-bash-v1.0.0.tar"
|
||||
}
|
||||
@@ -1,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
33
Src/Hyperion-bash/bin/cat
Normal file
@@ -0,0 +1,33 @@
|
||||
local args = {...}
|
||||
local name = syscall.getTask(syscall.getpid()).name
|
||||
local fs = require("sys.fs")
|
||||
|
||||
if not args[1] then
|
||||
while true do
|
||||
local content = syscall.read(0, 1024)
|
||||
if not content or content == "" then break end
|
||||
printInline(content)
|
||||
end
|
||||
print("")
|
||||
return
|
||||
end
|
||||
|
||||
for _, arg in ipairs(args) do
|
||||
local filePath = arg
|
||||
if filePath:sub(1,1) ~= "/" then
|
||||
filePath = syscall.getcwd().."/"..filePath
|
||||
end
|
||||
|
||||
if not fs.exists(filePath) then
|
||||
print(name..": Cannot access '"..arg.."': No such file.")
|
||||
else
|
||||
local fd = syscall.open(filePath, "r")
|
||||
while true do
|
||||
local content = syscall.read(fd, 1024)
|
||||
if not content or content == "" then break end
|
||||
printInline(content)
|
||||
end
|
||||
syscall.close(fd)
|
||||
end
|
||||
end
|
||||
print("")
|
||||
162
Src/Hyperion-bash/bin/chattr
Normal file
162
Src/Hyperion-bash/bin/chattr
Normal file
@@ -0,0 +1,162 @@
|
||||
--:Minify:--
|
||||
-- supports +i/-i (immutable) stored in the file's cmeta/xattr field
|
||||
local name = syscall.getTask(syscall.getpid()).name
|
||||
local cloptions = { R = false, help = false }
|
||||
local args = {}
|
||||
local modeStr = nil
|
||||
|
||||
for _, v in ipairs({ ... }) do
|
||||
if v:sub(1, 2) == "--" then
|
||||
local opt = v:sub(3)
|
||||
if cloptions[opt] == nil then
|
||||
print(name .. ": unrecognized option '" .. v .. "'")
|
||||
print("try '" .. name .. " --help' for more information.")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
cloptions[opt] = true
|
||||
elseif v:sub(1, 1) == "-" and not v:match("^%-[%+%-]") then
|
||||
local isFlag = true
|
||||
for i = 2, #v do
|
||||
local c = v:sub(i,i)
|
||||
if cloptions[c] ~= nil then
|
||||
cloptions[c] = true
|
||||
else
|
||||
isFlag = false; break
|
||||
end
|
||||
end
|
||||
if not isFlag then
|
||||
modeStr = v
|
||||
end
|
||||
elseif v:sub(1,1) == "+" or (v:sub(1,1) == "-" and v:match("^%-[a-zA-Z]")) then
|
||||
modeStr = v
|
||||
else
|
||||
table.insert(args, v)
|
||||
end
|
||||
end
|
||||
|
||||
if cloptions.help then
|
||||
print("Usage: " .. name .. " [OPTION]... +-= ATTRS FILE...")
|
||||
print("Change file attributes on a filesystem.")
|
||||
print("")
|
||||
print("Attributes:")
|
||||
print(" i immutable: file cannot be modified, renamed, or deleted")
|
||||
print(" a append-only: file can only be appended to")
|
||||
print("")
|
||||
print("Operators: +attr add, -attr remove")
|
||||
print("Example: " .. name .. " +i /etc/passwd")
|
||||
print("")
|
||||
print("Options:")
|
||||
print(" -R operate on files and directories recursively")
|
||||
print(" --help display this help and exit")
|
||||
return
|
||||
end
|
||||
|
||||
if not modeStr or #args < 1 then
|
||||
print(name .. ": missing operand")
|
||||
print("try '" .. name .. " --help' for more information.")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
local op = modeStr:sub(1, 1)
|
||||
local attrs = modeStr:sub(2)
|
||||
|
||||
if op ~= "+" and op ~= "-" then
|
||||
print(name .. ": invalid operator '" .. op .. "' (use + or -)")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
local function getXattr(path)
|
||||
local stat = pcall(function() return syscall.stat(path) end) and syscall.stat(path)
|
||||
if stat then return stat.xattr or "" end
|
||||
return ""
|
||||
end
|
||||
|
||||
local IMMUTABLE_TAG = "|i"
|
||||
local APPENDONLY_TAG = "|a"
|
||||
|
||||
local function attrTag(c)
|
||||
if c == "i" then return IMMUTABLE_TAG
|
||||
elseif c == "a" then return APPENDONLY_TAG
|
||||
else return nil end
|
||||
end
|
||||
|
||||
local function chattrPath(path)
|
||||
local stat = syscall.stat(path)
|
||||
if not stat then
|
||||
print(name .. ": cannot stat '" .. path .. "': No such file or directory")
|
||||
return false
|
||||
end
|
||||
if stat.etype == 0x01 then
|
||||
return true
|
||||
end
|
||||
|
||||
if not syscall.setxattr then
|
||||
print(name .. ": kernel does not expose setxattr syscall; cannot modify attributes")
|
||||
syscall.exit(1); return false
|
||||
end
|
||||
|
||||
local xattr = stat.xattr or ""
|
||||
|
||||
for i = 1, #attrs do
|
||||
local c = attrs:sub(i, i)
|
||||
local tag = attrTag(c)
|
||||
if not tag then
|
||||
print(name .. ": unsupported attribute '" .. c .. "'")
|
||||
syscall.exit(1); return false
|
||||
end
|
||||
local hasTag = xattr:find(tag, 1, true)
|
||||
if op == "+" and not hasTag then
|
||||
xattr = xattr .. tag
|
||||
elseif op == "-" and hasTag then
|
||||
xattr = xattr:gsub(tag:gsub("|", "%%|"), "")
|
||||
end
|
||||
end
|
||||
|
||||
local ok, err = pcall(syscall.setxattr, path, xattr)
|
||||
if not ok then
|
||||
print(name .. ": cannot set attributes on '" .. path .. "': " .. tostring(err))
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local function chattrRecursive(path)
|
||||
if not chattrPath(path) then return end
|
||||
if syscall.type(path) == "directory" then
|
||||
local ok, list = pcall(syscall.listdir, path)
|
||||
if ok then
|
||||
for _, entry in ipairs(list) do
|
||||
local child = path
|
||||
if child:sub(-1) ~= "/" then child = child .. "/" end
|
||||
chattrRecursive(child .. entry)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local cwd = syscall.getcwd()
|
||||
local function absPath(p)
|
||||
if p:sub(1,1) ~= "/" then p = cwd .. "/" .. p end
|
||||
return p
|
||||
end
|
||||
|
||||
if not syscall.setxattr then
|
||||
print(name .. ": kernel does not expose setxattr; attributes cannot be persisted")
|
||||
print(name .. ": add sys[\"setxattr\"] = vfs.setxattr to 10_vfs.kmod to enable this")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
local exitCode = 0
|
||||
for i = 1, #args do
|
||||
local path = absPath(args[i])
|
||||
if not syscall.exists(path) then
|
||||
print(name .. ": cannot access '" .. args[i] .. "': No such file or directory")
|
||||
exitCode = 1
|
||||
elseif cloptions.R then
|
||||
chattrRecursive(path)
|
||||
else
|
||||
if not chattrPath(path) then exitCode = 1 end
|
||||
end
|
||||
end
|
||||
|
||||
syscall.exit(exitCode)
|
||||
111
Src/Hyperion-bash/bin/chgrp
Normal file
111
Src/Hyperion-bash/bin/chgrp
Normal 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
262
Src/Hyperion-bash/bin/chmod
Normal 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
144
Src/Hyperion-bash/bin/chown
Normal 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)
|
||||
83
Src/Hyperion-bash/bin/chroot
Normal file
83
Src/Hyperion-bash/bin/chroot
Normal file
@@ -0,0 +1,83 @@
|
||||
--:Minify:--
|
||||
local name = syscall.getTask(syscall.getpid()).name
|
||||
local args = {}
|
||||
local cloptions = { help = false }
|
||||
|
||||
for _, v in ipairs({ ... }) do
|
||||
if v:sub(1, 2) == "--" then
|
||||
local opt = v:sub(3)
|
||||
if opt == "help" then
|
||||
cloptions.help = true
|
||||
else
|
||||
print(name .. ": unrecognized option '" .. v .. "'")
|
||||
print("try '" .. name .. " --help' for more information.")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
elseif v:sub(1, 1) == "-" then
|
||||
print(name .. ": invalid option '" .. v .. "'")
|
||||
print("try '" .. name .. " --help' for more information.")
|
||||
syscall.exit(1); return
|
||||
else
|
||||
table.insert(args, v)
|
||||
end
|
||||
end
|
||||
|
||||
if cloptions.help then
|
||||
print("Usage: " .. name .. " NEWROOT [COMMAND [ARG]...]")
|
||||
print("Run COMMAND with root directory set to NEWROOT.")
|
||||
print("If COMMAND is omitted, runs the current user's shell.")
|
||||
print("")
|
||||
print("Requires root (uid 0).")
|
||||
return
|
||||
end
|
||||
|
||||
if #args < 1 then
|
||||
print(name .. ": missing operand")
|
||||
print("try '" .. name .. " --help' for more information.")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
local euid = syscall.geteuid and syscall.geteuid() or syscall.getuid()
|
||||
if euid ~= 0 then
|
||||
print(name .. ": cannot change root directory: Permission denied")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
local newRoot = args[1]
|
||||
if newRoot:sub(1,1) ~= "/" then
|
||||
newRoot = syscall.getcwd() .. "/" .. newRoot
|
||||
end
|
||||
|
||||
if not syscall.exists(newRoot) then
|
||||
print(name .. ": cannot change root directory to '" .. args[1] .. "': No such file or directory")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
if syscall.type(newRoot) ~= "directory" then
|
||||
print(name .. ": '" .. args[1] .. "': Not a directory")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
local ok, err = pcall(syscall.chroot, newRoot)
|
||||
if not ok then
|
||||
print(name .. ": cannot change root directory to '" .. args[1] .. "': " .. tostring(err))
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
local shell
|
||||
if #args >= 2 then
|
||||
shell = args[2]
|
||||
else
|
||||
local uid = syscall.getuid()
|
||||
local pwent = syscall.getpasswd(uid)
|
||||
shell = (pwent and pwent.shell) or "/bin/hysh"
|
||||
end
|
||||
|
||||
local execArgs = {}
|
||||
for i = 3, #args do table.insert(execArgs, args[i]) end
|
||||
|
||||
local execOk, execErr = pcall(syscall.exec, shell, execArgs)
|
||||
if not execOk then
|
||||
print(name .. ": failed to run command '" .. shell .. "': " .. tostring(execErr))
|
||||
syscall.exit(127)
|
||||
end
|
||||
1
Src/Hyperion-bash/bin/clear
Normal file
1
Src/Hyperion-bash/bin/clear
Normal file
@@ -0,0 +1 @@
|
||||
syscall.devctl(1,"clear")
|
||||
2
Src/Hyperion-bash/bin/echo
Normal file
2
Src/Hyperion-bash/bin/echo
Normal file
@@ -0,0 +1,2 @@
|
||||
local args = {...}
|
||||
print(table.concat(args, " "))
|
||||
309
Src/Hyperion-bash/bin/help
Normal file
309
Src/Hyperion-bash/bin/help
Normal file
@@ -0,0 +1,309 @@
|
||||
--:Minify:--
|
||||
|
||||
local COMMANDS = {
|
||||
{ name="cd", usage="cd [dir]", desc="Change working directory. Use '-' to return to previous directory.", flags={} },
|
||||
{ name="pwd", usage="pwd", desc="Print current working directory.", flags={} },
|
||||
{ name="ls", usage="ls [-alh] [dir]", desc="List directory contents. Coloured by type: dirs=blue, symlinks=cyan, executables=green.", flags={
|
||||
{"-a","Show hidden files (starting with .)"},
|
||||
{"-l","Long format: permissions, owner, group, size, mtime, name"},
|
||||
{"-h","Human-readable file sizes (with -l)"},
|
||||
{"--help","Display help and exit"},
|
||||
}},
|
||||
{ name="find", usage="find [path] [-name PAT] [-type f|d|l] [-maxdepth N]", desc="Walk the filesystem tree and print matching paths.", flags={
|
||||
{"-name PAT", "Match filename against shell glob (* and ?)"},
|
||||
{"-type f|d|l","Filter by file, directory, or symlink"},
|
||||
{"-maxdepth N","Descend at most N directory levels"},
|
||||
{"-mindepth N","Skip entries shallower than N levels"},
|
||||
{"-empty", "Match empty files or empty directories"},
|
||||
}},
|
||||
{ name="cp", usage="cp [-rRp] SOURCE... DEST", desc="Copy files or directories.", flags={
|
||||
{"-r,-R","Recurse into directories"},
|
||||
{"-p", "Preserve permissions"},
|
||||
{"--help","Display help and exit"},
|
||||
}},
|
||||
{ name="mv", usage="mv [-f] SOURCE... DEST", desc="Move or rename files and directories.", flags={
|
||||
{"-f", "Do not prompt before overwriting (default)"},
|
||||
{"--help","Display help and exit"},
|
||||
}},
|
||||
{ name="rm", usage="rm [-rRf] FILE...", desc="Remove files or directories.", flags={
|
||||
{"-r,-R","Recursively remove directories and their contents"},
|
||||
{"-f", "Ignore nonexistent files, never prompt"},
|
||||
{"--help","Display help and exit"},
|
||||
}},
|
||||
{ name="touch", usage="touch FILE...", desc="Create an empty file, or no-op if it already exists.", flags={} },
|
||||
{ name="mkdir", usage="mkdir <dir>", desc="Create a directory.", flags={} },
|
||||
{ name="ln", usage="ln -s [-f] TARGET LINK", desc="Create a symbolic link. Multiple targets can be linked into a directory.", flags={
|
||||
{"-s", "Create a symbolic link (required; hard links not supported)"},
|
||||
{"-f", "Remove existing destination before creating link"},
|
||||
{"--help","Display help and exit"},
|
||||
}},
|
||||
{ name="cat", usage="cat [file...]", desc="Print file(s) to stdout. Reads stdin if no file given.", flags={} },
|
||||
{ name="head", usage="head [-n N] [file...]", desc="Print the first N lines of each file (default 10).", flags={
|
||||
{"-n N","Number of lines to print"},
|
||||
{"--help","Display help and exit"},
|
||||
}},
|
||||
{ name="tail", usage="tail [-n N] [file...]", desc="Print the last N lines of each file (default 10).", flags={
|
||||
{"-n N","Number of lines to print"},
|
||||
{"--help","Display help and exit"},
|
||||
}},
|
||||
{ name="wc", usage="wc [-lwc] [file...]", desc="Count lines, words, and bytes in files.", flags={
|
||||
{"-l","Print line count"},
|
||||
{"-w","Print word count"},
|
||||
{"-c","Print byte count"},
|
||||
{"--help","Display help and exit"},
|
||||
}},
|
||||
{ name="grep", usage="grep [-ivnlcrR] PATTERN [file...]", desc="Search for lines matching a Lua pattern.", flags={
|
||||
{"-i","Ignore case"},
|
||||
{"-v","Invert: select non-matching lines"},
|
||||
{"-n","Prefix output with line numbers"},
|
||||
{"-l","Print only filenames that contain a match"},
|
||||
{"-c","Print count of matching lines per file"},
|
||||
{"-r,-R","Recurse into directories"},
|
||||
{"--help","Display help and exit"},
|
||||
}},
|
||||
{ name="sed", usage="sed 's/PAT/REPL/' [file...]", desc="Stream editor. Applies substitution commands to each line.", flags={} },
|
||||
{ name="sort", usage="sort [-rnu] [file...]", desc="Sort lines of text.", flags={
|
||||
{"-r","Reverse the sort order"},
|
||||
{"-n","Numeric sort"},
|
||||
{"-u","Suppress duplicate lines"},
|
||||
{"--help","Display help and exit"},
|
||||
}},
|
||||
{ name="uniq", usage="uniq [-cdui] [input [output]]", desc="Filter adjacent duplicate lines.", flags={
|
||||
{"-c","Prefix each line with its repetition count"},
|
||||
{"-d","Print only lines that appear more than once"},
|
||||
{"-u","Print only lines that appear exactly once"},
|
||||
{"-i","Ignore case when comparing"},
|
||||
{"--help","Display help and exit"},
|
||||
}},
|
||||
{ name="tee", usage="tee [-a] [file...]", desc="Copy stdin to stdout and to each FILE simultaneously.", flags={
|
||||
{"-a","Append to files instead of overwriting"},
|
||||
{"--help","Display help and exit"},
|
||||
}},
|
||||
{ name="basename", usage="basename STRING [SUFFIX]", desc="Strip directory and optional suffix from a path.", flags={} },
|
||||
{ name="dirname", usage="dirname STRING...", desc="Strip the last component from a path.", flags={} },
|
||||
{ name="readlink", usage="readlink [-fenq] file...", desc="Print the target of a symbolic link.", flags={
|
||||
{"-f","Canonicalize: follow every symlink component"},
|
||||
{"-e","Like -f but all components must exist"},
|
||||
{"-n","Do not output trailing newline"},
|
||||
{"--help","Display help and exit"},
|
||||
}},
|
||||
{ name="stat", usage="stat file...", desc="Display file type, size, owner, group, and permissions.", flags={
|
||||
{"--help","Display help and exit"},
|
||||
}},
|
||||
{ name="chmod", usage="chmod [-R] MODE file...", desc="Change file permissions. MODE may be octal (755) or symbolic (u+x).", flags={
|
||||
{"-R", "Recurse into directories"},
|
||||
{"--help","Display help and exit"},
|
||||
}},
|
||||
{ name="chown", usage="chown [-R] USER[:GROUP] file...", desc="Change file owner and/or group.", flags={
|
||||
{"-R","Recurse into directories"},
|
||||
{"--help","Display help and exit"},
|
||||
}},
|
||||
{ name="chgrp", usage="chgrp [-R] GROUP file...", desc="Change file group ownership.", flags={
|
||||
{"-R","Recurse into directories"},
|
||||
{"--help","Display help and exit"},
|
||||
}},
|
||||
{ name="chattr", usage="chattr [+-=][attrs] file...", desc="Change file attributes.", flags={} },
|
||||
{ name="echo", usage="echo [text...]", desc="Print arguments to stdout.", flags={} },
|
||||
{ name="whoami", usage="whoami", desc="Print the current username.", flags={} },
|
||||
{ name="id", usage="id [username]", desc="Print user identity (uid, gid).", flags={} },
|
||||
{ name="ps", usage="ps", desc="List running tasks with pid, user, name, and status.", flags={} },
|
||||
{ name="hostname", usage="hostname [NAME]", desc="Print or set the system hostname.", flags={} },
|
||||
{ name="uname", usage="uname [-asnrm]", desc="Print system information (OS name, hostname, release, machine).", flags={
|
||||
{"-a","Print all fields"},
|
||||
{"-s","Kernel name"},
|
||||
{"-n","Node hostname"},
|
||||
{"-r","Kernel release"},
|
||||
{"-m","Machine hardware name"},
|
||||
{"--help","Display help and exit"},
|
||||
}},
|
||||
{ name="df", usage="df [-h] [path...]", desc="Report filesystem disk space usage.", flags={
|
||||
{"-h","Human-readable sizes (K, M, G)"},
|
||||
{"--help","Display help and exit"},
|
||||
}},
|
||||
{ name="stat", usage="stat file...", desc="Display file status: type, size, permissions, owner.", flags={} },
|
||||
{ name="env", usage="env [KEY=VAL]... [CMD]", desc="Print the environment, or run a command with modified environment.", flags={} },
|
||||
{ name="printenv", usage="printenv [NAME...]", desc="Print environment variable values (all if no names given).", flags={} },
|
||||
{ name="sleep", usage="sleep N[smhd]", desc="Pause for N seconds (or minutes/hours/days with m/h/d suffix).", flags={} },
|
||||
{ name="true", usage="true", desc="Do nothing, exit successfully (status 0).", flags={} },
|
||||
{ name="false", usage="false", desc="Do nothing, exit unsuccessfully (status 1).", flags={} },
|
||||
{ name="yes", usage="yes [text]", desc="Repeatedly print 'y' (or given text) until interrupted.", flags={} },
|
||||
{ name="mount", usage="mount [-o loop] [SRC DEST | ID MNT]", desc="Mount a loop device or show all current mounts.", flags={
|
||||
{"-o loop","Attach SRC as a loop device and mount at DEST in one step"},
|
||||
{"--help","Display help and exit"},
|
||||
}},
|
||||
{ name="umount", usage="umount [--no-detach] MOUNTPOINT | -l LOOPID", desc="Unmount a filesystem and auto-detach its loop device.", flags={
|
||||
{"--no-detach","Unmount but keep loop device attached"},
|
||||
{"-l LOOPID","Force-detach a loop device without unmounting"},
|
||||
{"--help","Display help and exit"},
|
||||
}},
|
||||
{ name="losetup", usage="losetup [-dil] [path]", desc="Attach a directory or .hfs image as a loop device.", flags={
|
||||
{"-d ID","Detach loop device"},
|
||||
{"-i path","Force image mode (even without .hfs extension)"},
|
||||
{"-l","List all attached loop devices"},
|
||||
{"--help","Display help and exit"},
|
||||
}},
|
||||
{ name="loimgcreate", usage="loimgcreate [-x] SRC DEST", desc="Pack a directory into a portable HFS image, or extract one.", flags={
|
||||
{"-x","Extract image to destination directory"},
|
||||
{"--help","Display help and exit"},
|
||||
}},
|
||||
{ name="useradd", usage="useradd [-p pw] [-g gid] [-d home] [-s shell] [-M] <user>", desc="Create a new user account.", flags={
|
||||
{"-p pw", "Set password"},
|
||||
{"-g gid", "Set primary group id"},
|
||||
{"-d home", "Set home directory (default /home/username)"},
|
||||
{"-s shell","Set login shell (default /bin/hysh)"},
|
||||
{"-M", "Do not create home directory"},
|
||||
}},
|
||||
{ name="userdel", usage="userdel [-r] <user>", desc="Delete a user account.", flags={
|
||||
{"-r","Also remove the user's home directory"},
|
||||
}},
|
||||
{ name="usermod", usage="usermod [-l name] [-p pw] [-g gid] [-d home] [-s shell] [-LU] <user>", desc="Modify an existing user account.", flags={
|
||||
{"-l name", "Rename the user"},
|
||||
{"-p pw", "Set new password"},
|
||||
{"-g gid", "Change primary group id"},
|
||||
{"-d home", "Change home directory"},
|
||||
{"-s shell","Change login shell"},
|
||||
{"-L", "Lock the account"},
|
||||
{"-U", "Unlock the account"},
|
||||
}},
|
||||
{ name="passwd", usage="passwd [username]", desc="Change a user password.", flags={} },
|
||||
{ name="lsusers", usage="lsusers", desc="List all user accounts with uid, gid, home, and shell.", flags={} },
|
||||
{ name="su", usage="su [username]", desc="Switch user. Defaults to root. Root can switch without a password.", flags={} },
|
||||
{ name="sudo", usage="sudo [-u user] CMD [args...]", desc="Run a command as another user (default root).", flags={
|
||||
{"-u user","Run as the specified user (name or uid)"},
|
||||
}},
|
||||
{ name="exit", usage="exit [N]", desc="Exit the shell with optional status code N.", flags={} },
|
||||
{ name="clear", usage="clear", desc="Clear the terminal screen.", flags={} },
|
||||
{ name="help", usage="help [command]", desc="Display this command reference. Pass a command name to filter.", flags={} },
|
||||
{ name="lua", usage="lua", desc="Interactive Lua REPL prompt.", flags={} },
|
||||
{ name="micro", usage="micro [file]", desc="Full-screen terminal text editor.", flags={} },
|
||||
{ name="hfetch", usage="hfetch", desc="Display system information in a neofetch-style layout.", flags={} },
|
||||
{ name="sysdump", usage="sysdump", desc="List all registered kernel syscalls.", flags={} },
|
||||
{ name="chroot", usage="chroot DIR [CMD]", desc="Run a command with a different root directory.", flags={} },
|
||||
}
|
||||
|
||||
do
|
||||
local seen = {}
|
||||
local deduped = {}
|
||||
for _, cmd in ipairs(COMMANDS) do
|
||||
if not seen[cmd.name] then
|
||||
seen[cmd.name] = true
|
||||
table.insert(deduped, cmd)
|
||||
end
|
||||
end
|
||||
COMMANDS = deduped
|
||||
end
|
||||
|
||||
local C_HEAD = 7
|
||||
local C_CMD = 5
|
||||
local C_USAGE = 1
|
||||
local C_DESC = 13
|
||||
local C_FLAG = 3
|
||||
local C_DIM = 12
|
||||
|
||||
local lines = {}
|
||||
local function push(text, col) lines[#lines+1] = {text, col or 1} end
|
||||
|
||||
push("HyperionOS Command Reference", C_HEAD)
|
||||
push(string.rep("=", 50), C_DIM)
|
||||
push("", 1)
|
||||
|
||||
local args = {...}
|
||||
local filter = args[1]
|
||||
|
||||
local function addCmd(cmd)
|
||||
push(cmd.name, C_CMD)
|
||||
push(" Usage: " .. cmd.usage, C_USAGE)
|
||||
push(" " .. cmd.desc, C_DESC)
|
||||
if #cmd.flags > 0 then
|
||||
for _, f in ipairs(cmd.flags) do
|
||||
push(" " .. f[1], C_FLAG)
|
||||
push(" " .. f[2], C_DESC)
|
||||
end
|
||||
end
|
||||
push("", 1)
|
||||
end
|
||||
|
||||
if filter then
|
||||
local found = false
|
||||
for _, cmd in ipairs(COMMANDS) do
|
||||
if cmd.name == filter then addCmd(cmd); found = true; break end
|
||||
end
|
||||
if not found then
|
||||
push("help: unknown command '" .. filter .. "'", 2)
|
||||
push("Run 'help' with no arguments for the full list.", C_DESC)
|
||||
end
|
||||
else
|
||||
push("Run 'help <command>' for details on a specific command.", C_DESC)
|
||||
push("", 1)
|
||||
for _, cmd in ipairs(COMMANDS) do addCmd(cmd) end
|
||||
end
|
||||
|
||||
local sizeStr = syscall.devctl(1, "size")
|
||||
local screenW = tonumber(sizeStr:match("^(%d+)")) or 51
|
||||
local screenH = tonumber(sizeStr:match(";(%d+)")) or 19
|
||||
local pageSize = screenH - 2
|
||||
|
||||
local scroll = 0
|
||||
local totalLines = #lines
|
||||
local dirty = true
|
||||
|
||||
local function render()
|
||||
syscall.devctl(1, "clear")
|
||||
syscall.devctl(1, "spos", 1, 1)
|
||||
for row = 1, pageSize do
|
||||
local li = scroll + row
|
||||
if li <= totalLines then
|
||||
local text, col = lines[li][1], lines[li][2]
|
||||
syscall.devctl(1, "sfgc", col)
|
||||
if #text > screenW then text = text:sub(1, screenW) end
|
||||
syscall.write(1, text .. "\n")
|
||||
else
|
||||
syscall.write(1, "\n")
|
||||
end
|
||||
end
|
||||
syscall.devctl(1, "sfgc", 16)
|
||||
syscall.devctl(1, "sbgc", 13)
|
||||
local pct = math.floor(math.min(100, (scroll + pageSize) / totalLines * 100))
|
||||
local status = string.format(" help -- line %d/%d (%d%%) [up/down: scroll q: quit] ",
|
||||
scroll + 1, totalLines, pct)
|
||||
if #status > screenW then status = status:sub(1, screenW) end
|
||||
syscall.devctl(1, "spos", 1, screenH)
|
||||
syscall.write(1, status .. string.rep(" ", screenW - #status))
|
||||
syscall.devctl(1, "sfgc", 1)
|
||||
syscall.devctl(1, "sbgc", 16)
|
||||
dirty = false
|
||||
end
|
||||
|
||||
if totalLines <= pageSize then
|
||||
syscall.devctl(1, "clear")
|
||||
syscall.devctl(1, "spos", 1, 1)
|
||||
for _, line in ipairs(lines) do
|
||||
syscall.devctl(1, "sfgc", line[2])
|
||||
syscall.write(1, line[1] .. "\n")
|
||||
end
|
||||
syscall.devctl(1, "sfgc", 1)
|
||||
return
|
||||
end
|
||||
|
||||
render()
|
||||
while true do
|
||||
local ch = syscall.read(0)
|
||||
if not ch or ch == "" then
|
||||
elseif ch == "q" or ch == "Q" then
|
||||
break
|
||||
elseif ch == "\17" then
|
||||
if scroll > 0 then scroll = scroll - 1; dirty = true end
|
||||
elseif ch == "\18" then
|
||||
if scroll + pageSize < totalLines then scroll = scroll + 1; dirty = true end
|
||||
elseif ch == "\19" then
|
||||
scroll = math.max(0, scroll - pageSize); dirty = true
|
||||
elseif ch == "\20" then
|
||||
scroll = math.min(totalLines - pageSize, scroll + pageSize); dirty = true
|
||||
end
|
||||
if dirty then render() end
|
||||
end
|
||||
|
||||
syscall.devctl(1, "clear")
|
||||
syscall.devctl(1, "spos", 1, 1)
|
||||
syscall.devctl(1, "sfgc", 1)
|
||||
syscall.devctl(1, "sbgc", 16)
|
||||
104
Src/Hyperion-bash/bin/hfetch
Normal file
104
Src/Hyperion-bash/bin/hfetch
Normal 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
998
Src/Hyperion-bash/bin/hysh
Normal 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
|
||||
94
Src/Hyperion-bash/bin/hyshex
Normal file
94
Src/Hyperion-bash/bin/hyshex
Normal file
@@ -0,0 +1,94 @@
|
||||
--:Minify:--
|
||||
syscall.open("/dev/tty/TTY1","r")
|
||||
syscall.open("/dev/tty/TTY1","w")
|
||||
syscall.open("/dev/null","r")
|
||||
syscall.devctl(1,"clear")
|
||||
syscall.devctl(1,"sfgc",1)
|
||||
syscall.devctl(1,"spos",1,1)
|
||||
print("HyperionOS hysh Shell")
|
||||
local str=""
|
||||
local stopInput=false
|
||||
local proc=0
|
||||
local fs=require("sys.fs")
|
||||
local timeout=false
|
||||
syscall.setEnviron("SHELL","simpleshell")
|
||||
printInline("> ")
|
||||
syscall.sigcatch(function(sig)
|
||||
if sig==1 then
|
||||
syscall.kill(proc)
|
||||
print("Terminated")
|
||||
printInline("> ")
|
||||
stopInput=false
|
||||
end
|
||||
end)
|
||||
|
||||
while true do
|
||||
if not stopInput then
|
||||
local input=syscall.read(0)
|
||||
if input then
|
||||
if input=="\b" then
|
||||
if #str>0 then
|
||||
str=str:sub(1,#str-1)
|
||||
printInline("\b")
|
||||
end
|
||||
elseif input=="\n" then
|
||||
print("")
|
||||
stopInput=true
|
||||
if str == "" then
|
||||
printInline("> ")
|
||||
stopInput=false
|
||||
else
|
||||
local path=nil
|
||||
local split=string.split(str, " ")
|
||||
if fs.exists("/bin/"..split[1]) then
|
||||
path="/bin/"..split[1]
|
||||
elseif fs.exists("/bin/"..split[1]..".lua") then
|
||||
path="/bin/"..split[1]..".lua"
|
||||
end
|
||||
if not path then
|
||||
print("Program not found")
|
||||
printInline("> ")
|
||||
stopInput=false
|
||||
else
|
||||
local text = fs.readAllText(path)
|
||||
local program, err = load(text, path)
|
||||
if not program then
|
||||
print(err)
|
||||
printInline("> ")
|
||||
end
|
||||
proc = syscall.spawn(function(...)
|
||||
syscall.open("/dev/tty/TTY1","r")
|
||||
syscall.open("/dev/tty/TTY1","w")
|
||||
syscall.open("/dev/null","w")
|
||||
program(...)
|
||||
end, path, nil, {table.unpack(split, 2)})
|
||||
end
|
||||
str=""
|
||||
end
|
||||
else
|
||||
str=str..input
|
||||
printInline(input)
|
||||
end
|
||||
timeout=false
|
||||
else
|
||||
timeout=true
|
||||
end
|
||||
else
|
||||
local exited, code = syscall.collect(proc)
|
||||
if exited then
|
||||
if code then
|
||||
print("\nTask exited with code:\n"..tostring(code))
|
||||
end
|
||||
printInline("> ")
|
||||
stopInput=false
|
||||
end
|
||||
timeout=true
|
||||
end
|
||||
if timeout then
|
||||
if stopInput then
|
||||
sleep(.5)
|
||||
else
|
||||
sleep(.05)
|
||||
end
|
||||
end
|
||||
end
|
||||
19
Src/Hyperion-bash/bin/id
Normal file
19
Src/Hyperion-bash/bin/id
Normal file
@@ -0,0 +1,19 @@
|
||||
--:Minify:--
|
||||
local args = {...}
|
||||
local uid
|
||||
|
||||
if args[1] then
|
||||
uid = syscall.getuid(args[1])
|
||||
if not uid then
|
||||
print("id: user '" .. args[1] .. "' does not exist")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
else
|
||||
uid = syscall.getuid()
|
||||
end
|
||||
|
||||
local pwent = syscall.getpasswd(uid)
|
||||
local name = (pwent and pwent.username) or tostring(uid)
|
||||
local gid = (pwent and pwent.gid) or uid
|
||||
|
||||
print(string.format("uid=%d(%s) gid=%d(%s)", uid, name, gid, name))
|
||||
96
Src/Hyperion-bash/bin/ln
Normal file
96
Src/Hyperion-bash/bin/ln
Normal file
@@ -0,0 +1,96 @@
|
||||
--:Minify:--
|
||||
local name = syscall.getTask(syscall.getpid()).name
|
||||
local cloptions = { s = false, f = false, help = false }
|
||||
local args = {}
|
||||
|
||||
for _, v in ipairs({ ... }) do
|
||||
if v:sub(1, 2) == "--" then
|
||||
local opt = v:sub(3)
|
||||
if cloptions[opt] == nil then
|
||||
print(name .. ": unrecognized option '" .. v .. "'")
|
||||
print("try '" .. name .. " --help' for more information.")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
cloptions[opt] = true
|
||||
elseif v:sub(1, 1) == "-" then
|
||||
for i = 2, #v do
|
||||
local opt = v:sub(i, i)
|
||||
if cloptions[opt] == nil then
|
||||
print(name .. ": invalid option '-" .. opt .. "'")
|
||||
print("try '" .. name .. " --help' for more information.")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
cloptions[opt] = true
|
||||
end
|
||||
else
|
||||
table.insert(args, v)
|
||||
end
|
||||
end
|
||||
|
||||
if cloptions.help then
|
||||
print("Usage: " .. name .. " [OPTION]... TARGET LINK_NAME")
|
||||
print(" " .. name .. " [OPTION]... TARGET... DIRECTORY")
|
||||
print("Create links between files.")
|
||||
print("")
|
||||
print("Options:")
|
||||
print(" -s make symbolic links instead of hard links")
|
||||
print(" -f remove existing destination files")
|
||||
print(" --help display this help and exit")
|
||||
print("")
|
||||
print("With no -s, hard links are not supported (filesystem limitation).")
|
||||
print("Use -s for symbolic links.")
|
||||
return
|
||||
end
|
||||
|
||||
if #args < 2 then
|
||||
print(name .. ": missing operand")
|
||||
print("try '" .. name .. " --help' for more information.")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
if not cloptions.s then
|
||||
print(name .. ": hard links are not supported; use -s for symbolic links")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
local dest = args[#args]
|
||||
local destDir = syscall.type(dest) == "directory"
|
||||
|
||||
local function cwd()
|
||||
local d = syscall.getcwd()
|
||||
if d:sub(-1) ~= "/" then d = d .. "/" end
|
||||
return d
|
||||
end
|
||||
|
||||
local function absPath(p)
|
||||
if p:sub(1,1) ~= "/" then p = cwd() .. p end
|
||||
return p
|
||||
end
|
||||
|
||||
for i = 1, #args - 1 do
|
||||
local target = args[i]
|
||||
local linkPath
|
||||
|
||||
if destDir then
|
||||
local basename = target:match("[^/]+$") or target
|
||||
linkPath = absPath(dest)
|
||||
if linkPath:sub(-1) ~= "/" then linkPath = linkPath .. "/" end
|
||||
linkPath = linkPath .. basename
|
||||
else
|
||||
linkPath = absPath(dest)
|
||||
end
|
||||
|
||||
if cloptions.f and syscall.exists(linkPath) then
|
||||
local ok, err = pcall(syscall.remove, linkPath)
|
||||
if not ok then
|
||||
print(name .. ": cannot remove '" .. linkPath .. "': " .. tostring(err))
|
||||
syscall.exit(1); return
|
||||
end
|
||||
end
|
||||
|
||||
local ok, err = pcall(syscall.symlink, target, linkPath)
|
||||
if not ok then
|
||||
print(name .. ": failed to create symlink '" .. linkPath .. "' -> '" .. target .. "': " .. tostring(err))
|
||||
syscall.exit(1); return
|
||||
end
|
||||
end
|
||||
166
Src/Hyperion-bash/bin/login
Normal file
166
Src/Hyperion-bash/bin/login
Normal 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
|
||||
157
Src/Hyperion-bash/bin/loimgcreate
Normal file
157
Src/Hyperion-bash/bin/loimgcreate
Normal file
@@ -0,0 +1,157 @@
|
||||
--:Minify:--
|
||||
-- Usage:
|
||||
-- loimgcreate <srcdir> <image.hfs> create image from directory
|
||||
-- loimgcreate -x <image.hfs> <dest> extract image back to a directory
|
||||
-- loimgcreate --help
|
||||
|
||||
local name = syscall.getTask(syscall.getpid()).name
|
||||
local args, opts = {}, { x=false, help=false }
|
||||
|
||||
for _, v in ipairs({...}) do
|
||||
if v:sub(1,2) == "--" then
|
||||
local o = v:sub(3)
|
||||
if o == "help" then opts.help = true
|
||||
else print(name..": unrecognised option '"..v.."'")
|
||||
print("try '"..name.." --help' for more information.")
|
||||
syscall.exit(1); return end
|
||||
elseif v:sub(1,1) == "-" then
|
||||
for i = 2, #v do
|
||||
local c = v:sub(i,i)
|
||||
if opts[c] ~= nil then opts[c] = true
|
||||
else print(name..": invalid option '-"..c.."'")
|
||||
print("try '"..name.." --help' for more information.")
|
||||
syscall.exit(1); return end
|
||||
end
|
||||
else
|
||||
table.insert(args, v)
|
||||
end
|
||||
end
|
||||
|
||||
if opts.help then
|
||||
print("Usage: "..name.." <srcdir> <image.hfs>")
|
||||
print(" "..name.." -x <image.hfs> <destdir>")
|
||||
print("")
|
||||
print("Pack a directory into a portable HFS image file, or extract one.")
|
||||
print("")
|
||||
print(" <srcdir> <image.hfs> recursively pack srcdir into image.hfs")
|
||||
print(" -x <image.hfs> <dest> extract image.hfs into dest (created if needed)")
|
||||
print("")
|
||||
print("HFS images can be mounted with:")
|
||||
print(" mount -o loop /path/to/image.hfs /mnt/point")
|
||||
print("")
|
||||
print("Requires root.")
|
||||
return
|
||||
end
|
||||
|
||||
local fs = require("sys.fs")
|
||||
|
||||
if opts.x then
|
||||
if #args < 2 then
|
||||
print(name..": -x requires <image.hfs> and <destdir>")
|
||||
print("try '"..name.." --help' for more information.")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
local imgPath = args[1]
|
||||
local destPath = args[2]
|
||||
if imgPath:sub(1,1) ~= "/" then imgPath = syscall.getcwd().."/"..imgPath end
|
||||
if destPath:sub(1,1) ~= "/" then destPath = syscall.getcwd().."/"..destPath end
|
||||
|
||||
local tmpMnt = "/tmp/._loimgcreate_"..tostring(math.random(100000,999999))
|
||||
|
||||
local ok1, loopId = pcall(syscall.losetup, imgPath, true)
|
||||
if not ok1 then
|
||||
print(name..": losetup: "..tostring(loopId)); syscall.exit(1); return
|
||||
end
|
||||
|
||||
local ok2, merr = pcall(syscall.mount, tmpMnt, loopId)
|
||||
if not ok2 then
|
||||
pcall(syscall.lodetach, loopId)
|
||||
print(name..": mount: "..tostring(merr)); syscall.exit(1); return
|
||||
end
|
||||
|
||||
if not fs.isDir(destPath) then
|
||||
local ok3, derr = pcall(syscall.mkdir, destPath)
|
||||
if not ok3 then
|
||||
pcall(syscall.umount, tmpMnt); pcall(syscall.lodetach, loopId)
|
||||
print(name..": mkdir '"..args[2].."': "..tostring(derr))
|
||||
syscall.exit(1); return
|
||||
end
|
||||
end
|
||||
|
||||
local count = 0
|
||||
local function copyTree(src, dst)
|
||||
local entries = fs.list(src)
|
||||
if not entries then return end
|
||||
for _, ent in ipairs(entries) do
|
||||
local srcFull = src:gsub("/$","").."/"..ent
|
||||
local dstFull = dst:gsub("/$","").."/"..ent
|
||||
if fs.isDir(srcFull) then
|
||||
pcall(syscall.mkdir, dstFull)
|
||||
copyTree(srcFull, dstFull)
|
||||
else
|
||||
local ok, rfd = pcall(syscall.open, srcFull, "r")
|
||||
if ok then
|
||||
local ok2, wfd = pcall(syscall.open, dstFull, "w")
|
||||
if ok2 then
|
||||
local ok3, data = pcall(syscall.read, rfd, 65536*16)
|
||||
if ok3 and data then pcall(syscall.write, wfd, data) end
|
||||
pcall(syscall.close, wfd)
|
||||
count = count + 1
|
||||
end
|
||||
pcall(syscall.close, rfd)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
copyTree(tmpMnt, destPath)
|
||||
|
||||
pcall(syscall.umount, tmpMnt)
|
||||
pcall(syscall.lodetach, loopId)
|
||||
|
||||
syscall.devctl(1, "sfgc", 10)
|
||||
print(name..": extracted "..count.." file(s) to "..destPath)
|
||||
syscall.devctl(1, "sfgc", 1)
|
||||
return
|
||||
end
|
||||
|
||||
if #args < 2 then
|
||||
print(name..": missing operands — need <srcdir> and <image.hfs>")
|
||||
print("try '"..name.." --help' for more information.")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
local srcPath = args[1]
|
||||
local imgPath = args[2]
|
||||
if srcPath:sub(1,1) ~= "/" then srcPath = syscall.getcwd().."/"..srcPath end
|
||||
if imgPath:sub(1,1) ~= "/" then imgPath = syscall.getcwd().."/"..imgPath end
|
||||
|
||||
if not fs.isDir(srcPath) then
|
||||
print(name..": '"..args[1].."': not a directory")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
local ok, imgStr = pcall(syscall.loimgcreate, srcPath)
|
||||
if not ok then
|
||||
local msg = tostring(imgStr)
|
||||
if msg:find("EPERM") then msg = "Permission denied"
|
||||
elseif msg:find("ENOTDIR") then msg = "'"..args[1].."': not a directory" end
|
||||
print(name..": "..msg); syscall.exit(1); return
|
||||
end
|
||||
|
||||
local ok2, werr = pcall(syscall.loimgwrite, imgStr, imgPath)
|
||||
if not ok2 then
|
||||
print(name..": write '"..args[2].."': "..tostring(werr))
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
local lineCount = 0
|
||||
for _ in imgStr:gmatch("\n") do lineCount = lineCount + 1 end
|
||||
local byteCount = #imgStr
|
||||
|
||||
syscall.devctl(1, "sfgc", 10)
|
||||
print(name..": image written to "..imgPath)
|
||||
syscall.devctl(1, "sfgc", 14)
|
||||
print(string.format(" %d records, %d bytes", lineCount - 1, byteCount))
|
||||
syscall.devctl(1, "sfgc", 1)
|
||||
427
Src/Hyperion-bash/bin/looptest
Normal file
427
Src/Hyperion-bash/bin/looptest
Normal file
@@ -0,0 +1,427 @@
|
||||
--:Minify:--
|
||||
|
||||
local passed, failed = 0, 0
|
||||
|
||||
local function pass(msg) syscall.devctl(1,"sfgc",10); print(" PASS "..msg); syscall.devctl(1,"sfgc",1) end
|
||||
local function fail(msg) syscall.devctl(1,"sfgc",2); print(" FAIL "..msg); syscall.devctl(1,"sfgc",1) end
|
||||
local function info(msg) syscall.devctl(1,"sfgc",14); print(" .... "..msg); syscall.devctl(1,"sfgc",1) end
|
||||
local function head(msg) syscall.devctl(1,"sfgc",4); print("\n"..msg); syscall.devctl(1,"sfgc",1) end
|
||||
|
||||
local function check(label, ok, err)
|
||||
if ok then passed = passed + 1; pass(label)
|
||||
else failed = failed + 1; fail(label.." - "..tostring(err)) end
|
||||
end
|
||||
|
||||
local function writeFile(path, data)
|
||||
local ok, fd = pcall(syscall.open, path, "w")
|
||||
if not ok then return false, fd end
|
||||
local ok2, err = pcall(syscall.write, fd, data)
|
||||
pcall(syscall.close, fd)
|
||||
return ok2, err
|
||||
end
|
||||
|
||||
local function readFile(path)
|
||||
local ok, fd = pcall(syscall.open, path, "r")
|
||||
if not ok then return false, fd end
|
||||
local ok2, data = pcall(syscall.read, fd, 65536)
|
||||
pcall(syscall.close, fd)
|
||||
return ok2, data
|
||||
end
|
||||
|
||||
local function rmrf(path)
|
||||
local t = syscall.type(path)
|
||||
if t == "directory" then
|
||||
local ok, entries = pcall(syscall.listdir, path)
|
||||
if ok then
|
||||
for _, name in ipairs(entries) do
|
||||
rmrf(path:gsub("/$","").."/"..name)
|
||||
end
|
||||
end
|
||||
pcall(syscall.remove, path)
|
||||
elseif t == "file" then
|
||||
pcall(syscall.remove, path)
|
||||
end
|
||||
end
|
||||
|
||||
local SCRATCH = "/tmp/looptest_scratch"
|
||||
local SRC_DIR = SCRATCH.."/src"
|
||||
local BIND_MNT = SCRATCH.."/bind_mnt"
|
||||
local IMG_PATH = SCRATCH.."/test.hfs"
|
||||
local IMG_MNT = SCRATCH.."/img_mnt"
|
||||
local BIND_LOOP = nil
|
||||
local IMG_LOOP = nil
|
||||
|
||||
rmrf(SCRATCH)
|
||||
pcall(syscall.mkdir, SCRATCH)
|
||||
pcall(syscall.mkdir, SRC_DIR)
|
||||
pcall(syscall.mkdir, BIND_MNT)
|
||||
pcall(syscall.mkdir, IMG_MNT)
|
||||
pcall(syscall.mkdir, SRC_DIR.."/subdir")
|
||||
|
||||
writeFile(SRC_DIR.."/hello.txt", "hello from hyperion\n")
|
||||
writeFile(SRC_DIR.."/data.txt", "line1\nline2\nline3\n")
|
||||
writeFile(SRC_DIR.."/subdir/deep.txt", "deep file\n")
|
||||
|
||||
head("[ 1 ] bind mode - losetup on a directory")
|
||||
do
|
||||
local ok, id = pcall(syscall.losetup, SRC_DIR)
|
||||
check("losetup(dir) returns a loop id", ok and type(id) == "string" and id:sub(1,4) == "loop", id)
|
||||
if ok then
|
||||
BIND_LOOP = id
|
||||
info("attached as "..id)
|
||||
end
|
||||
end
|
||||
|
||||
head("[ 2 ] bind mode - mount and read files")
|
||||
do
|
||||
if BIND_LOOP then
|
||||
local mok = pcall(syscall.mount, BIND_MNT, BIND_LOOP)
|
||||
check("mount(bind_mnt, "..BIND_LOOP..")", mok, "mount failed")
|
||||
|
||||
if mok then
|
||||
local lok, entries = pcall(syscall.listdir, BIND_MNT)
|
||||
check("listdir through bind mount", lok and type(entries) == "table", entries)
|
||||
|
||||
local rok, data = readFile(BIND_MNT.."/hello.txt")
|
||||
check("read hello.txt through bind", rok and data == "hello from hyperion\n",
|
||||
rok and ("got: "..tostring(data):sub(1,40)) or tostring(data))
|
||||
|
||||
local rok2, data2 = readFile(BIND_MNT.."/subdir/deep.txt")
|
||||
check("read subdir/deep.txt through bind", rok2 and data2 == "deep file\n",
|
||||
rok2 and ("got: "..tostring(data2)) or tostring(data2))
|
||||
end
|
||||
else
|
||||
check("mount (skipped - no loop id)", false, "losetup failed in [1]")
|
||||
end
|
||||
end
|
||||
|
||||
head("[ 3 ] bind mode - write through loop mount, verify on host")
|
||||
do
|
||||
if BIND_LOOP then
|
||||
local wok, werr = writeFile(BIND_MNT.."/written.txt", "written via loop\n")
|
||||
check("write new file through bind mount", wok, werr)
|
||||
|
||||
local rok, data = readFile(BIND_MNT.."/written.txt")
|
||||
check("read back through bind mount", rok and data == "written via loop\n",
|
||||
rok and ("got: "..tostring(data)) or tostring(data))
|
||||
|
||||
local rok2, data2 = readFile(SRC_DIR.."/written.txt")
|
||||
check("file visible on host path (bind is transparent)", rok2 and data2 == "written via loop\n",
|
||||
rok2 and ("got: "..tostring(data2)) or tostring(data2))
|
||||
else
|
||||
check("write (skipped)", false, "bind mount not set up")
|
||||
end
|
||||
end
|
||||
|
||||
head("[ 4 ] bind mode - lodetach while mounted returns EBUSY")
|
||||
do
|
||||
if BIND_LOOP then
|
||||
local ok = pcall(syscall.lodetach, BIND_LOOP)
|
||||
check("lodetach while mounted is refused (EBUSY)", not ok, "should have errored")
|
||||
else
|
||||
check("lodetach busy check (skipped)", false, "no bind loop")
|
||||
end
|
||||
end
|
||||
|
||||
head("[ 5 ] bind mode - umount then lodetach")
|
||||
do
|
||||
if BIND_LOOP then
|
||||
local uok = pcall(syscall.umount, BIND_MNT)
|
||||
check("umount(bind_mnt)", uok, "umount failed")
|
||||
|
||||
local dok = pcall(syscall.lodetach, BIND_LOOP)
|
||||
check("lodetach after umount", dok, "lodetach failed")
|
||||
|
||||
if dok then BIND_LOOP = nil end
|
||||
else
|
||||
check("umount+lodetach (skipped)", false, "no bind loop")
|
||||
end
|
||||
end
|
||||
|
||||
head("[ 6 ] loimgcreate - serialise directory to HFS image")
|
||||
do
|
||||
local ok, imgStr = pcall(syscall.loimgcreate, SRC_DIR)
|
||||
check("loimgcreate(srcdir) returns a string", ok and type(imgStr) == "string" and #imgStr > 0, imgStr)
|
||||
|
||||
if ok then
|
||||
info("image size: "..#imgStr.." bytes")
|
||||
|
||||
local isBHFS = imgStr:sub(1, 4) == "BHFS"
|
||||
check("image has BHFS magic header", isBHFS,
|
||||
"got: "..imgStr:sub(1,4):gsub(".", function(c) return string.format("%02X ", c:byte()) end))
|
||||
check("image has correct version byte (0x01)", imgStr:byte(5) == 1,
|
||||
"version byte: "..tostring(imgStr:byte(5)))
|
||||
check("image contains FILE record (type=0x01)", imgStr:find("\001", 9, true) ~= nil, "no FILE type byte found")
|
||||
check("image contains DIR record (type=0x02)", imgStr:find("\002", 9, true) ~= nil, "no DIR type byte found")
|
||||
check("image ends with END record (type=0xFF)", imgStr:byte(#imgStr) == 0xFF,
|
||||
"last byte: 0x"..string.format("%02X", imgStr:byte(#imgStr)))
|
||||
|
||||
local wok, werr = pcall(syscall.loimgwrite, imgStr, IMG_PATH)
|
||||
check("loimgwrite writes image file", wok, werr)
|
||||
check("image file exists on disk", syscall.type(IMG_PATH) == "file", "file not found")
|
||||
end
|
||||
end
|
||||
|
||||
head("[ 7 ] HFS image - losetup attaches image file")
|
||||
do
|
||||
if syscall.type(IMG_PATH) == "file" then
|
||||
local ok, id = pcall(syscall.losetup, IMG_PATH)
|
||||
check("losetup(image.hfs) returns loop id", ok and type(id) == "string", id)
|
||||
if ok then
|
||||
IMG_LOOP = id
|
||||
info("image attached as "..id)
|
||||
|
||||
local lok, devs = pcall(syscall.lolist)
|
||||
local found = false
|
||||
if lok then
|
||||
for lid, info_entry in pairs(devs) do
|
||||
if lid == id then found = true end
|
||||
end
|
||||
end
|
||||
check("lolist() contains new image device", found, "not found in lolist")
|
||||
end
|
||||
else
|
||||
check("losetup image (skipped - no image file)", false, "image not created in [6]")
|
||||
end
|
||||
end
|
||||
|
||||
head("[ 8 ] HFS image - mount and read files")
|
||||
do
|
||||
if IMG_LOOP then
|
||||
local mok = pcall(syscall.mount, IMG_MNT, IMG_LOOP)
|
||||
check("mount(img_mnt, "..IMG_LOOP..")", mok, "mount failed")
|
||||
|
||||
if mok then
|
||||
local lok, entries = pcall(syscall.listdir, IMG_MNT)
|
||||
check("listdir through image mount returns table", lok and type(entries) == "table", entries)
|
||||
if lok then
|
||||
local hasHello = false
|
||||
local hasSubdir = false
|
||||
for _, e in ipairs(entries) do
|
||||
if e == "hello.txt" then hasHello = true end
|
||||
if e == "subdir" then hasSubdir = true end
|
||||
end
|
||||
check("hello.txt visible in image root", hasHello, "not listed")
|
||||
check("subdir/ visible in image root", hasSubdir, "not listed")
|
||||
end
|
||||
|
||||
local rok, data = readFile(IMG_MNT.."/hello.txt")
|
||||
check("read hello.txt from image", rok and data == "hello from hyperion\n",
|
||||
rok and ("got: "..tostring(data):sub(1,40)) or tostring(data))
|
||||
|
||||
local rok2, data2 = readFile(IMG_MNT.."/data.txt")
|
||||
check("read data.txt from image", rok2 and data2 == "line1\nline2\nline3\n",
|
||||
rok2 and ("got: "..tostring(data2)) or tostring(data2))
|
||||
end
|
||||
else
|
||||
check("image mount read (skipped)", false, "no image loop")
|
||||
end
|
||||
end
|
||||
|
||||
head("[ 9 ] HFS image - write new files into image mount")
|
||||
do
|
||||
if IMG_LOOP then
|
||||
local wok, werr = writeFile(IMG_MNT.."/newfile.txt", "created inside image\n")
|
||||
check("write new file into image mount", wok, werr)
|
||||
|
||||
local rok, data = readFile(IMG_MNT.."/newfile.txt")
|
||||
check("read back newly written file", rok and data == "created inside image\n",
|
||||
rok and ("got: "..tostring(data)) or tostring(data))
|
||||
|
||||
local wok2, werr2 = writeFile(IMG_MNT.."/hello.txt", "overwritten\n")
|
||||
check("overwrite existing file in image", wok2, werr2)
|
||||
|
||||
local rok2, data2 = readFile(IMG_MNT.."/hello.txt")
|
||||
check("overwritten content reads back correctly", rok2 and data2 == "overwritten\n",
|
||||
rok2 and ("got: "..tostring(data2)) or tostring(data2))
|
||||
|
||||
local rok3, orig = readFile(IMG_PATH)
|
||||
check("disk image file is unchanged after in-memory write",
|
||||
rok3 and orig and orig:find("/hello%.txt") ~= nil,
|
||||
rok3 and "filename record missing from image" or tostring(orig))
|
||||
else
|
||||
check("image write test (skipped)", false, "image not mounted")
|
||||
end
|
||||
end
|
||||
|
||||
head("[ 10 ] HFS image - sub-directory traversal")
|
||||
do
|
||||
if IMG_LOOP then
|
||||
local t = syscall.type(IMG_MNT.."/subdir")
|
||||
check("type(subdir) == 'directory'", t == "directory", "got: "..tostring(t))
|
||||
|
||||
local lok, entries = pcall(syscall.listdir, IMG_MNT.."/subdir")
|
||||
check("listdir(subdir) works", lok and type(entries) == "table", entries)
|
||||
|
||||
local rok, data = readFile(IMG_MNT.."/subdir/deep.txt")
|
||||
check("read subdir/deep.txt from image", rok and data == "deep file\n",
|
||||
rok and ("got: "..tostring(data)) or tostring(data))
|
||||
|
||||
local mok = pcall(syscall.mkdir, IMG_MNT.."/subdir/newdir")
|
||||
check("mkdir inside image mount", mok, "mkdir failed")
|
||||
check("new dir has type 'directory'",
|
||||
syscall.type(IMG_MNT.."/subdir/newdir") == "directory",
|
||||
"wrong type")
|
||||
|
||||
local wok, werr = writeFile(IMG_MNT.."/subdir/newdir/x.txt", "x\n")
|
||||
check("write file in newly created subdir", wok, werr)
|
||||
else
|
||||
check("subdir traversal (skipped)", false, "image not mounted")
|
||||
end
|
||||
end
|
||||
|
||||
head("[ 11 ] HFS image - lodetach while mounted returns EBUSY")
|
||||
do
|
||||
if IMG_LOOP then
|
||||
local ok = pcall(syscall.lodetach, IMG_LOOP)
|
||||
check("lodetach image while mounted is refused", not ok, "should have errored EBUSY")
|
||||
else
|
||||
check("lodetach busy (skipped)", false, "no image loop")
|
||||
end
|
||||
end
|
||||
|
||||
head("[ 12 ] HFS image - umount then lodetach")
|
||||
do
|
||||
if IMG_LOOP then
|
||||
local uok = pcall(syscall.umount, IMG_MNT)
|
||||
check("umount(img_mnt)", uok, "umount failed")
|
||||
|
||||
local dok = pcall(syscall.lodetach, IMG_LOOP)
|
||||
check("lodetach after umount", dok, "lodetach failed")
|
||||
|
||||
if dok then
|
||||
local lok, devs = pcall(syscall.lolist)
|
||||
local found = false
|
||||
if lok then
|
||||
for lid in pairs(devs) do
|
||||
if lid == IMG_LOOP then found = true end
|
||||
end
|
||||
end
|
||||
check("lolist no longer shows detached device", not found, "still present in lolist")
|
||||
IMG_LOOP = nil
|
||||
end
|
||||
else
|
||||
check("image umount+lodetach (skipped)", false, "no image loop")
|
||||
end
|
||||
end
|
||||
|
||||
head("[ 13 ] lolist - reflects attached device count")
|
||||
do
|
||||
local ok1, id1 = pcall(syscall.losetup, SRC_DIR)
|
||||
local ok2, id2 = pcall(syscall.losetup, SRC_DIR)
|
||||
check("attach first device for lolist test", ok1, id1)
|
||||
check("attach second device for lolist test", ok2, id2)
|
||||
|
||||
if ok1 and ok2 then
|
||||
local lok, devs = pcall(syscall.lolist)
|
||||
check("lolist() succeeds", lok, devs)
|
||||
if lok then
|
||||
local found1, found2 = false, false
|
||||
for lid in pairs(devs) do
|
||||
if lid == id1 then found1 = true end
|
||||
if lid == id2 then found2 = true end
|
||||
end
|
||||
check("lolist contains first device", found1, "missing "..id1)
|
||||
check("lolist contains second device", found2, "missing "..id2)
|
||||
local count = 0
|
||||
for _ in pairs(devs) do count = count + 1 end
|
||||
info("lolist shows "..count.." device(s)")
|
||||
end
|
||||
end
|
||||
|
||||
if ok1 then pcall(syscall.lodetach, id1) end
|
||||
if ok2 then pcall(syscall.lodetach, id2) end
|
||||
end
|
||||
|
||||
head("[ 14 ] losetup - non-existent path returns error")
|
||||
do
|
||||
local ok = pcall(syscall.losetup, "/tmp/does_not_exist_xyz_looptest")
|
||||
check("losetup on missing path errors", not ok, "should have errored")
|
||||
end
|
||||
|
||||
head("[ 15 ] mount - same loop device cannot be mounted twice")
|
||||
do
|
||||
local ok, id = pcall(syscall.losetup, SRC_DIR)
|
||||
check("losetup for double-mount test", ok, id)
|
||||
if ok then
|
||||
local m1ok = pcall(syscall.mount, BIND_MNT, id)
|
||||
check("first mount succeeds", m1ok, "mount failed")
|
||||
|
||||
if m1ok then
|
||||
local m2ok = pcall(syscall.mount, IMG_MNT, id)
|
||||
check("second mount of same device is refused", not m2ok, "should have errored EBUSY")
|
||||
pcall(syscall.umount, BIND_MNT)
|
||||
end
|
||||
pcall(syscall.lodetach, id)
|
||||
end
|
||||
end
|
||||
|
||||
head("[ 16 ] loimgcreate - on a regular file returns ENOTDIR")
|
||||
do
|
||||
local ok = pcall(syscall.loimgcreate, IMG_PATH)
|
||||
check("loimgcreate on a file errors (ENOTDIR)", not ok, "should have errored")
|
||||
end
|
||||
|
||||
head("[ 17 ] HFS image - binary round-trip (all byte values)")
|
||||
do
|
||||
local bytes = {}
|
||||
for i = 0, 255 do bytes[i+1] = string.char(i) end
|
||||
local binData = table.concat(bytes)
|
||||
|
||||
local binSrc = SCRATCH.."/binsrc"
|
||||
pcall(syscall.mkdir, binSrc)
|
||||
writeFile(binSrc.."/binary.bin", binData)
|
||||
|
||||
local ok1, imgStr = pcall(syscall.loimgcreate, binSrc)
|
||||
check("loimgcreate handles binary content", ok1, imgStr)
|
||||
|
||||
if ok1 then
|
||||
local binImg = SCRATCH.."/binary.hfs"
|
||||
pcall(syscall.loimgwrite, imgStr, binImg)
|
||||
|
||||
local ok2, lid = pcall(syscall.losetup, binImg)
|
||||
check("losetup on binary image", ok2, lid)
|
||||
|
||||
if ok2 then
|
||||
local mnt = SCRATCH.."/binmnt"
|
||||
pcall(syscall.mkdir, mnt)
|
||||
local mok = pcall(syscall.mount, mnt, lid)
|
||||
check("mount binary image", mok, "mount failed")
|
||||
|
||||
if mok then
|
||||
local rok, readBack = readFile(mnt.."/binary.bin")
|
||||
check("binary file readable from image", rok, readBack)
|
||||
check("binary data round-trips without corruption",
|
||||
rok and readBack == binData,
|
||||
rok and string.format("length in=%d out=%d", #binData, #(readBack or "")) or tostring(readBack))
|
||||
pcall(syscall.umount, mnt)
|
||||
end
|
||||
pcall(syscall.lodetach, lid)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
head("[ 18 ] second-run safety - lolist is empty after full cleanup")
|
||||
do
|
||||
local lok, devs = pcall(syscall.lolist)
|
||||
check("lolist() call succeeds", lok, devs)
|
||||
if lok then
|
||||
local count = 0
|
||||
for _ in pairs(devs) do count = count + 1 end
|
||||
check("no leftover loop devices after all tests", count == 0,
|
||||
count.." device(s) still attached: "..
|
||||
(function()
|
||||
local ids = {}
|
||||
for id in pairs(devs) do ids[#ids+1] = id end
|
||||
return table.concat(ids, ", ")
|
||||
end)())
|
||||
end
|
||||
end
|
||||
|
||||
rmrf(SCRATCH)
|
||||
|
||||
print("")
|
||||
syscall.devctl(1, "sfgc", failed == 0 and 10 or 2)
|
||||
print(string.format("Results: %d passed, %d failed", passed, failed))
|
||||
syscall.devctl(1, "sfgc", 1)
|
||||
if failed > 0 then syscall.exit(1) end
|
||||
129
Src/Hyperion-bash/bin/losetup
Normal file
129
Src/Hyperion-bash/bin/losetup
Normal file
@@ -0,0 +1,129 @@
|
||||
--:Minify:--
|
||||
-- Usage:
|
||||
-- losetup <path> attach directory or .hfs image; print loop id
|
||||
-- losetup -d <id> detach loop device
|
||||
-- losetup -l list attached loop devices
|
||||
-- losetup -i <path> force image mode (even without .hfs extension)
|
||||
-- losetup --help
|
||||
|
||||
local name = syscall.getTask(syscall.getpid()).name
|
||||
local args, opts = {}, { d=false, l=false, i=false, help=false }
|
||||
|
||||
for _, v in ipairs({...}) do
|
||||
if v:sub(1,2) == "--" then
|
||||
local o = v:sub(3)
|
||||
if o == "help" then opts.help = true
|
||||
else print(name..": unrecognised option '"..v.."'")
|
||||
print("try '"..name.." --help' for more information.")
|
||||
syscall.exit(1); return end
|
||||
elseif v:sub(1,1) == "-" then
|
||||
for i = 2, #v do
|
||||
local c = v:sub(i,i)
|
||||
if opts[c] ~= nil then opts[c] = true
|
||||
else print(name..": invalid option '-"..c.."'")
|
||||
print("try '"..name.." --help' for more information.")
|
||||
syscall.exit(1); return end
|
||||
end
|
||||
else
|
||||
table.insert(args, v)
|
||||
end
|
||||
end
|
||||
|
||||
if opts.help then
|
||||
print("Usage: "..name.." <path>")
|
||||
print(" "..name.." -i <path>")
|
||||
print(" "..name.." -d <id>")
|
||||
print(" "..name.." -l")
|
||||
print("")
|
||||
print("Manage loop devices.")
|
||||
print("")
|
||||
print(" <path> attach a directory (bind) or .hfs image file")
|
||||
print(" -i <path> force image mode for the given file")
|
||||
print(" -d <id> detach loop device by id (must be unmounted first)")
|
||||
print(" -l list all currently attached loop devices")
|
||||
print("")
|
||||
print("Requires root. Loop device ids look like loop0, loop1, …")
|
||||
return
|
||||
end
|
||||
|
||||
if opts.l then
|
||||
local ok, devs = pcall(syscall.lolist)
|
||||
if not ok then
|
||||
print(name..": "..tostring(devs)); syscall.exit(1); return
|
||||
end
|
||||
local any = false
|
||||
local ids = {}
|
||||
for id in pairs(devs) do ids[#ids+1] = id end
|
||||
table.sort(ids)
|
||||
for _, id in ipairs(ids) do
|
||||
any = true
|
||||
local info = devs[id]
|
||||
local mode = (type(info) == "table" and info.mode) or "bind"
|
||||
local path = (type(info) == "table" and info.path) or tostring(info)
|
||||
local colour = mode == "image" and 5 or 4
|
||||
syscall.devctl(1, "sfgc", 3)
|
||||
printInline(string.format("%-10s", id))
|
||||
syscall.devctl(1, "sfgc", colour)
|
||||
printInline(string.format("%-7s", "["..mode.."]"))
|
||||
syscall.devctl(1, "sfgc", 1)
|
||||
print(" "..path)
|
||||
end
|
||||
if not any then
|
||||
syscall.devctl(1, "sfgc", 14)
|
||||
print(name..": no loop devices attached")
|
||||
syscall.devctl(1, "sfgc", 1)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
if opts.d then
|
||||
if #args < 1 then
|
||||
print(name..": -d requires a loop device id")
|
||||
print("try '"..name.." --help' for more information.")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
local id = args[1]
|
||||
local ok, err = pcall(syscall.lodetach, id)
|
||||
if not ok then
|
||||
local msg = tostring(err)
|
||||
if msg:find("EPERM") then msg = "Permission denied"
|
||||
elseif msg:find("ENXIO") then msg = "no such loop device '"..id.."'"
|
||||
elseif msg:find("EBUSY") then msg = "device '"..id.."' is still mounted, unmount first"
|
||||
end
|
||||
print(name..": "..msg); syscall.exit(1); return
|
||||
end
|
||||
syscall.devctl(1, "sfgc", 10)
|
||||
print(name..": detached "..id)
|
||||
syscall.devctl(1, "sfgc", 1)
|
||||
return
|
||||
end
|
||||
|
||||
if #args < 1 then
|
||||
print(name..": missing path operand")
|
||||
print("try '"..name.." --help' for more information.")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
local path = args[1]
|
||||
if path:sub(1,1) ~= "/" then
|
||||
path = syscall.getcwd().."/"..path
|
||||
end
|
||||
|
||||
local ftype = syscall.type and syscall.type(path)
|
||||
if not (ftype == "file" or ftype == "directory") then
|
||||
print(name..": '"..args[1].."': no such file or directory")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
local ok, result = pcall(syscall.losetup, path, opts.i or nil)
|
||||
if not ok then
|
||||
local msg = tostring(result)
|
||||
if msg:find("EPERM") then msg = "Permission denied"
|
||||
elseif msg:find("ENOENT") then msg = "'"..args[1].."': no such file"
|
||||
elseif msg:find("EINVAL") then msg = "'"..args[1].."': not a directory or .hfs image"
|
||||
elseif msg:find("EIO") then msg = "'"..args[1].."': I/O error reading image"
|
||||
end
|
||||
print(name..": "..msg); syscall.exit(1); return
|
||||
end
|
||||
|
||||
print(result)
|
||||
193
Src/Hyperion-bash/bin/ls
Normal file
193
Src/Hyperion-bash/bin/ls
Normal file
@@ -0,0 +1,193 @@
|
||||
--:Minify:--
|
||||
local cloptions = {
|
||||
a = false,
|
||||
h = false,
|
||||
l = false,
|
||||
help = false,
|
||||
}
|
||||
local inpArgs = { ... }
|
||||
local args = {}
|
||||
local name = syscall.getTask(syscall.getpid()).name
|
||||
|
||||
for _, v in pairs(inpArgs) do
|
||||
if v:sub(1, 2) == "--" then
|
||||
local opt = v:sub(3)
|
||||
if cloptions[opt] == nil then
|
||||
print(name .. ": unrecognized option '" .. v .. "'.")
|
||||
print("try '" .. name .. " --help' for more information.")
|
||||
return
|
||||
end
|
||||
cloptions[opt] = true
|
||||
elseif v:sub(1, 1) == "-" then
|
||||
for i = 2, #v do
|
||||
local opt = v:sub(i, i)
|
||||
if cloptions[opt] == nil then
|
||||
print(name .. ": invalid option '-" .. opt .. "'.")
|
||||
print("try '" .. name .. " --help' for more information.")
|
||||
return
|
||||
end
|
||||
cloptions[opt] = true
|
||||
end
|
||||
else
|
||||
table.insert(args, v)
|
||||
end
|
||||
end
|
||||
|
||||
if cloptions.help then
|
||||
print("Usage: " .. name .. " [OPTION]... [DIR]")
|
||||
print("List all entries in the specified DIRectory, or cwd if not specified.")
|
||||
print("")
|
||||
print("Options:")
|
||||
print(" -a do not ignore entries starting with .")
|
||||
print(" -h with -l, print sizes in human readable format")
|
||||
print(" -l use a long listing format")
|
||||
print(" --help display this help and exit")
|
||||
return
|
||||
end
|
||||
|
||||
local fs = require("sys.fs")
|
||||
local dir = args[1] or ""
|
||||
if dir:sub(1, 1) ~= "/" then
|
||||
dir = syscall.getcwd() .. "/" .. dir
|
||||
end
|
||||
if dir:sub(-1) ~= "/" then dir = dir .. "/" end
|
||||
|
||||
if not fs.isDir(dir) then
|
||||
print(name .. ": cannot access '" .. (args[1] or dir) .. "': no such directory")
|
||||
return
|
||||
end
|
||||
|
||||
local function permStr(perms, etype)
|
||||
local function b(n) return math.floor(perms / (2^n)) % 2 == 1 end
|
||||
local t
|
||||
if etype == 0x01 then t = "l"
|
||||
elseif etype == nil then t = "-"
|
||||
else t = "-" end
|
||||
|
||||
local ur = b(5) and "r" or "-"
|
||||
local uw = b(4) and "w" or "-"
|
||||
local ux = b(9) and (b(6) and "s" or "x") or (b(6) and "S" or "-")
|
||||
local gr = b(3) and "r" or "-"
|
||||
local gw = b(2) and "w" or "-"
|
||||
local gx = b(8) and "x" or "-"
|
||||
local wr = b(1) and "r" or "-"
|
||||
local ww = b(0) and "w" or "-"
|
||||
local wx = b(7) and "x" or "-"
|
||||
|
||||
return t .. ur .. uw .. ux .. gr .. gw .. gx .. wr .. ww .. wx
|
||||
end
|
||||
|
||||
local sizePrefixes = { "K", "M", "G", "T" }
|
||||
local function humanSize(size)
|
||||
local scale = 0
|
||||
while size >= 1024 and scale < #sizePrefixes do
|
||||
size = size / 1024
|
||||
scale = scale + 1
|
||||
end
|
||||
if scale == 0 then return tostring(size) end
|
||||
if size < 10 then
|
||||
return string.format("%.1f%s", size, sizePrefixes[scale])
|
||||
end
|
||||
return math.floor(size) .. sizePrefixes[scale]
|
||||
end
|
||||
|
||||
local screenSizeStr = syscall.devctl(1, "size")
|
||||
local sizeX = tonumber(screenSizeStr:match("^(%d+)")) or 80
|
||||
|
||||
local list = fs.list(dir)
|
||||
if not cloptions.a then
|
||||
for i = #list, 1, -1 do
|
||||
if list[i]:sub(1, 1) == "." then table.remove(list, i) end
|
||||
end
|
||||
end
|
||||
table.sort(list)
|
||||
|
||||
if #list == 0 then return end
|
||||
|
||||
if cloptions.l then
|
||||
for _, v in ipairs(list) do
|
||||
local fullPath = dir .. v
|
||||
local stat = syscall.lstat and syscall.lstat(fullPath) or syscall.stat(fullPath)
|
||||
local isDir = fs.isDir(fullPath)
|
||||
local isSym = stat and stat.etype == 0x01
|
||||
|
||||
local typeChar
|
||||
if isSym then typeChar = "l"
|
||||
elseif isDir then typeChar = "d"
|
||||
else typeChar = "-" end
|
||||
|
||||
local pstr
|
||||
if stat and stat.perms then
|
||||
pstr = permStr(stat.perms, stat.etype)
|
||||
else
|
||||
pstr = typeChar .. "---------"
|
||||
end
|
||||
|
||||
local size = (stat and stat.size) or 0
|
||||
local sizeStr = cloptions.h and humanSize(size) or tostring(size)
|
||||
|
||||
local mtime = (stat and stat.modified) and math.floor(stat.modified / 1000) or 0
|
||||
|
||||
local owner = (stat and tostring(stat.owner)) or "0"
|
||||
local group = (stat and tostring(stat.group)) or "0"
|
||||
|
||||
printInline(pstr .. " " .. owner .. " " .. group .. " ")
|
||||
printInline(string.format("%6s", sizeStr) .. " ")
|
||||
printInline(tostring(mtime) .. " ")
|
||||
|
||||
if isSym then
|
||||
syscall.devctl(1, "sfgc", 6)
|
||||
printInline(v)
|
||||
syscall.devctl(1, "sfgc", 1)
|
||||
local ok, target = pcall(syscall.readlink, fullPath)
|
||||
if ok then
|
||||
printInline(" -> ")
|
||||
local targetExists = pcall(syscall.stat, fullPath)
|
||||
syscall.devctl(1, "sfgc", targetExists and 6 or 2)
|
||||
printInline(target)
|
||||
syscall.devctl(1, "sfgc", 1)
|
||||
end
|
||||
elseif isDir then
|
||||
syscall.devctl(1, "sfgc", 4)
|
||||
printInline(v)
|
||||
syscall.devctl(1, "sfgc", 1)
|
||||
else
|
||||
local isExec = stat and stat.perms and (math.floor(stat.perms / (2^9)) % 2 == 1)
|
||||
syscall.devctl(1, "sfgc", isExec and 3 or 1)
|
||||
printInline(v)
|
||||
syscall.devctl(1, "sfgc", 1)
|
||||
end
|
||||
print("")
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
local colWidth = 0
|
||||
for _, v in ipairs(list) do
|
||||
if #v + 2 > colWidth then colWidth = #v + 2 end
|
||||
end
|
||||
local numCols = math.max(1, math.floor(sizeX / colWidth))
|
||||
|
||||
for i, v in ipairs(list) do
|
||||
local fullPath = dir .. v
|
||||
local isDir = fs.isDir(fullPath)
|
||||
local stat = syscall.lstat and syscall.lstat(fullPath) or syscall.stat(fullPath)
|
||||
local isSym = stat and stat.etype == 0x01
|
||||
|
||||
if isSym then
|
||||
syscall.devctl(1, "sfgc", 6)
|
||||
elseif isDir then
|
||||
syscall.devctl(1, "sfgc", 4)
|
||||
else
|
||||
local isExec = stat and stat.perms and (math.floor(stat.perms / (2^9)) % 2 == 1)
|
||||
syscall.devctl(1, "sfgc", isExec and 3 or 1)
|
||||
end
|
||||
|
||||
printInline(v)
|
||||
syscall.devctl(1, "sfgc", 1)
|
||||
printInline((" "):rep(colWidth - #v))
|
||||
|
||||
if i % numCols == 0 then print("") end
|
||||
end
|
||||
|
||||
if #list % numCols ~= 0 then print("") end
|
||||
19
Src/Hyperion-bash/bin/lsusers
Normal file
19
Src/Hyperion-bash/bin/lsusers
Normal file
@@ -0,0 +1,19 @@
|
||||
--:Minify:--
|
||||
local users = syscall.listusers()
|
||||
if not users or #users == 0 then
|
||||
print("No users found.")
|
||||
return
|
||||
end
|
||||
|
||||
syscall.devctl(1,"sfgc",13)
|
||||
print(string.format("%-6s %-6s %-16s %-20s %s", "UID", "GID", "Username", "Home", "Shell"))
|
||||
print(string.rep("-", 65))
|
||||
syscall.devctl(1,"sfgc",1)
|
||||
|
||||
for _, u in ipairs(users) do
|
||||
local lock_marker = u.locked and " [locked]" or ""
|
||||
if u.locked then syscall.devctl(1,"sfgc",2) end
|
||||
print(string.format("%-6d %-6d %-16s %-20s %s%s",
|
||||
u.uid, u.gid, u.username, u.homedir, u.shell, lock_marker))
|
||||
if u.locked then syscall.devctl(1,"sfgc",1) end
|
||||
end
|
||||
325
Src/Hyperion-bash/bin/lua
Normal file
325
Src/Hyperion-bash/bin/lua
Normal file
@@ -0,0 +1,325 @@
|
||||
--:Minify:--
|
||||
local C_PROMPT = 7
|
||||
local C_CONT = 13
|
||||
local C_OUT = 5
|
||||
local C_ERR = 2
|
||||
local C_KEY = 3
|
||||
local C_STR = 9
|
||||
local C_NUM = 10
|
||||
local C_BOOL = 8
|
||||
local C_NIL = 12
|
||||
local C_TABLE = 13
|
||||
|
||||
local function c(col) syscall.devctl(1, "sfgc", col) end
|
||||
local function w(s) syscall.write(1, tostring(s)) end
|
||||
|
||||
local MAX_DEPTH = 6
|
||||
local MAX_ENTRIES = 64
|
||||
|
||||
local function prettyVal(val, indent, seen)
|
||||
indent = indent or 0
|
||||
seen = seen or {}
|
||||
local t = type(val)
|
||||
|
||||
if t == "nil" then
|
||||
c(C_NIL); w("nil")
|
||||
elseif t == "boolean" then
|
||||
c(C_BOOL); w(tostring(val))
|
||||
elseif t == "number" then
|
||||
c(C_NUM)
|
||||
if val ~= val then
|
||||
w("nan")
|
||||
elseif val == math.huge then
|
||||
w("inf")
|
||||
elseif val == -math.huge then
|
||||
w("-inf")
|
||||
elseif val == math.floor(val) and math.abs(val) < 1e15 then
|
||||
w(tostring(math.floor(val)))
|
||||
else
|
||||
w(tostring(val))
|
||||
end
|
||||
elseif t == "string" then
|
||||
c(C_STR)
|
||||
local s = string.format("%q", val)
|
||||
w(s)
|
||||
elseif t == "function" then
|
||||
c(C_TABLE); w(tostring(val))
|
||||
elseif t == "table" then
|
||||
if seen[val] then
|
||||
c(C_TABLE); w("<circular " .. tostring(val) .. ">")
|
||||
return
|
||||
end
|
||||
if indent >= MAX_DEPTH then
|
||||
c(C_TABLE); w("<table " .. tostring(val) .. ">")
|
||||
return
|
||||
end
|
||||
seen[val] = true
|
||||
|
||||
local pad = string.rep(" ", indent)
|
||||
local padIn = string.rep(" ", indent + 1)
|
||||
|
||||
local arrKeys = {}
|
||||
local hashKeys = {}
|
||||
local arrMax = #val
|
||||
|
||||
for k in pairs(val) do
|
||||
if type(k) == "number" and k >= 1 and k <= arrMax and k == math.floor(k) then
|
||||
arrKeys[#arrKeys+1] = k
|
||||
else
|
||||
hashKeys[#hashKeys+1] = k
|
||||
end
|
||||
end
|
||||
table.sort(arrKeys)
|
||||
|
||||
table.sort(hashKeys, function(a, b)
|
||||
local ta, tb = type(a), type(b)
|
||||
if ta == tb then
|
||||
if ta == "string" then return a < b end
|
||||
if ta == "number" then return a < b end
|
||||
return tostring(a) < tostring(b)
|
||||
end
|
||||
return ta < tb
|
||||
end)
|
||||
|
||||
local total = #arrKeys + #hashKeys
|
||||
if total == 0 then
|
||||
c(C_TABLE); w("{}")
|
||||
seen[val] = nil
|
||||
return
|
||||
end
|
||||
|
||||
c(C_TABLE); w("{\n")
|
||||
|
||||
local shown = 0
|
||||
local function printEntry(k, v, isLast)
|
||||
shown = shown + 1
|
||||
if shown > MAX_ENTRIES then return true end
|
||||
w(padIn)
|
||||
if type(k) == "number" and arrKeys[k] then
|
||||
else
|
||||
if type(k) == "string" and k:match("^[a-zA-Z_][a-zA-Z0-9_]*$") then
|
||||
c(C_KEY); w(k)
|
||||
else
|
||||
c(C_TABLE); w("[")
|
||||
prettyVal(k, indent+1, seen)
|
||||
c(C_TABLE); w("]")
|
||||
end
|
||||
c(C_TABLE); w(" = ")
|
||||
end
|
||||
prettyVal(v, indent+1, seen)
|
||||
c(C_TABLE)
|
||||
if not isLast then w(",") end
|
||||
w("\n")
|
||||
end
|
||||
|
||||
for i, k in ipairs(arrKeys) do
|
||||
w(padIn)
|
||||
prettyVal(val[k], indent+1, seen)
|
||||
c(C_TABLE)
|
||||
if i < total then w(",") end
|
||||
w("\n")
|
||||
shown = shown + 1
|
||||
if shown >= MAX_ENTRIES then
|
||||
c(C_NIL); w(padIn .. "-- ..." .. (total - shown) .. " more entries\n")
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if shown < MAX_ENTRIES then
|
||||
for i, k in ipairs(hashKeys) do
|
||||
local isLast = (shown + 1 >= total)
|
||||
w(padIn)
|
||||
if type(k) == "string" and k:match("^[a-zA-Z_][a-zA-Z0-9_]*$") then
|
||||
c(C_KEY); w(k)
|
||||
else
|
||||
c(C_TABLE); w("[")
|
||||
prettyVal(k, indent+1, seen)
|
||||
c(C_TABLE); w("]")
|
||||
end
|
||||
c(C_TABLE); w(" = ")
|
||||
prettyVal(val[k], indent+1, seen)
|
||||
c(C_TABLE)
|
||||
shown = shown + 1
|
||||
if shown < total then w(",") end
|
||||
w("\n")
|
||||
if shown >= MAX_ENTRIES then
|
||||
local rem = total - shown
|
||||
if rem > 0 then
|
||||
c(C_NIL); w(padIn .. "-- ..." .. rem .. " more entries\n")
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
c(C_TABLE); w(pad .. "}")
|
||||
seen[val] = nil
|
||||
else
|
||||
c(C_TABLE); w(tostring(val))
|
||||
end
|
||||
c(1)
|
||||
end
|
||||
|
||||
local function printResults(...)
|
||||
local n = select("#", ...)
|
||||
if n == 0 then return end
|
||||
for i = 1, n do
|
||||
if i > 1 then c(C_TABLE); w("\t") end
|
||||
prettyVal(select(i, ...), 0, {})
|
||||
end
|
||||
w("\n")
|
||||
c(1)
|
||||
end
|
||||
|
||||
local luaEnv = setmetatable({}, {__index = _ENV})
|
||||
luaEnv._G = luaEnv
|
||||
|
||||
luaEnv.print = function(...)
|
||||
local n = select("#", ...)
|
||||
for i = 1, n do
|
||||
if i > 1 then w("\t") end
|
||||
prettyVal(select(i, ...), 0, {})
|
||||
end
|
||||
w("\n")
|
||||
c(1)
|
||||
end
|
||||
|
||||
luaEnv.pp = function(val)
|
||||
prettyVal(val, 0, {})
|
||||
w("\n")
|
||||
c(1)
|
||||
end
|
||||
|
||||
luaEnv.exit = setmetatable({}, {
|
||||
__tostring = function() return "function: exit()" end,
|
||||
__call = function() syscall.exit() end,
|
||||
})
|
||||
|
||||
local function compile(code)
|
||||
local exprFn = load("return " .. code, "@lua", "t", luaEnv)
|
||||
if exprFn then return exprFn, true end
|
||||
local stmtFn, err = load(code, "@lua", "t", luaEnv)
|
||||
return stmtFn, false, err
|
||||
end
|
||||
|
||||
local function isIncomplete(code)
|
||||
local _, err = load(code, "@lua", "t", luaEnv)
|
||||
return err and (err:find("<eof>") ~= nil or err:find("'end'") ~= nil
|
||||
or err:find("'then'") ~= nil or err:find("'until'") ~= nil)
|
||||
end
|
||||
|
||||
local function cleanErr(msg)
|
||||
return tostring(msg)
|
||||
:gsub("^%[string .-%]:", "")
|
||||
:gsub("^@lua:", "")
|
||||
:gsub("stack traceback:.*", "")
|
||||
:match("^%s*(.-)%s*$")
|
||||
end
|
||||
|
||||
local function runCode(code)
|
||||
local fn, isExpr, err = compile(code)
|
||||
if not fn then
|
||||
c(C_ERR); w("[error] "); c(1); w(cleanErr(err) .. "\n")
|
||||
return
|
||||
end
|
||||
|
||||
local results = table.pack(xpcall(fn, debug.traceback))
|
||||
local ok = table.remove(results, 1)
|
||||
results.n = results.n - 1
|
||||
|
||||
if not ok then
|
||||
c(C_ERR); w("[error] "); c(1); w(cleanErr(results[1]) .. "\n")
|
||||
elseif isExpr and results.n > 0 then
|
||||
c(C_OUT); w("= ")
|
||||
printResults(table.unpack(results, 1, results.n))
|
||||
end
|
||||
end
|
||||
|
||||
local function getUserInput(prompt, history)
|
||||
c(C_PROMPT); w(prompt); c(1)
|
||||
local pos = syscall.devctl(1, "gpos")
|
||||
local ox = tonumber(pos:sub(1, pos:find(";")-1))
|
||||
local oy = tonumber(pos:sub(pos:find(";")+1))
|
||||
|
||||
local input = ""
|
||||
local cursor = 1
|
||||
local histIdx = 0
|
||||
local blink = false
|
||||
local dirty = true
|
||||
|
||||
local function redraw()
|
||||
syscall.devctl(1, "spos", ox, oy)
|
||||
w(input:sub(1, cursor-1))
|
||||
if blink then syscall.devctl(1,"sfgc",16); syscall.devctl(1,"sbgc",1) end
|
||||
w(cursor > #input and " " or input:sub(cursor, cursor))
|
||||
syscall.devctl(1,"sfgc",1); syscall.devctl(1,"sbgc",16)
|
||||
w(input:sub(cursor+1) .. " ")
|
||||
dirty = false
|
||||
end
|
||||
|
||||
while true do
|
||||
local key = syscall.read(0)
|
||||
if key and key ~= "" then
|
||||
if key == "\19" then
|
||||
if cursor > 1 then cursor = cursor - 1; dirty = true end
|
||||
elseif key == "\20" then
|
||||
if cursor <= #input then cursor = cursor + 1; dirty = true end
|
||||
elseif key == "\17" then
|
||||
if history and histIdx < #history then
|
||||
histIdx = histIdx + 1
|
||||
input = history[#history - histIdx + 1]
|
||||
cursor = #input + 1; dirty = true
|
||||
end
|
||||
elseif key == "\18" then
|
||||
if histIdx > 1 then
|
||||
histIdx = histIdx - 1
|
||||
input = history[#history - histIdx + 1]
|
||||
cursor = #input + 1; dirty = true
|
||||
elseif histIdx == 1 then
|
||||
histIdx = 0; input = ""; cursor = 1; dirty = true
|
||||
end
|
||||
elseif key == "\b" then
|
||||
if cursor > 1 then
|
||||
input = input:sub(1, cursor-2) .. input:sub(cursor)
|
||||
cursor = cursor - 1; dirty = true
|
||||
end
|
||||
elseif key == "\n" then
|
||||
syscall.devctl(1,"sfgc",1); syscall.devctl(1,"sbgc",16)
|
||||
syscall.devctl(1,"spos",ox,oy)
|
||||
w(input .. " \n")
|
||||
return input
|
||||
else
|
||||
input = input:sub(1, cursor-1) .. key .. input:sub(cursor)
|
||||
cursor = cursor + 1; dirty = true
|
||||
end
|
||||
end
|
||||
local nb = (math.floor(syscall.getUptime() / 500) % 2) == 0
|
||||
if nb ~= blink then blink = nb; dirty = true end
|
||||
if dirty then redraw() end
|
||||
end
|
||||
end
|
||||
|
||||
syscall.devctl(1, "clear")
|
||||
syscall.devctl(1, "spos", 1, 1)
|
||||
c(C_PROMPT); w("HyperionOS " .. _VERSION .. "\n")
|
||||
c(C_NIL)
|
||||
w("Interactive Lua REPL. exit() to quit.\n\n")
|
||||
c(1)
|
||||
|
||||
local history = {}
|
||||
|
||||
while true do
|
||||
local code = getUserInput("lua> ", history)
|
||||
if code == "" then goto continue end
|
||||
|
||||
while isIncomplete(code) do
|
||||
code = code .. "\n" .. getUserInput("... ", nil)
|
||||
end
|
||||
|
||||
if code ~= history[#history] then
|
||||
history[#history+1] = code
|
||||
end
|
||||
|
||||
runCode(code)
|
||||
::continue::
|
||||
end
|
||||
50
Src/Hyperion-bash/bin/luaold
Normal file
50
Src/Hyperion-bash/bin/luaold
Normal file
@@ -0,0 +1,50 @@
|
||||
--:Minify:--
|
||||
print("HyperionOS lua")
|
||||
local str=""
|
||||
local stopInput=false
|
||||
local timeout=false
|
||||
local luaEnv=setmetatable({},{__index=_ENV})
|
||||
printInline("> ")
|
||||
while true do
|
||||
local input=syscall.read(0)
|
||||
if input then
|
||||
if input=="\b" then
|
||||
if #str>0 then
|
||||
str=str:sub(1,#str-1)
|
||||
printInline("\b")
|
||||
end
|
||||
elseif input=="\n" then
|
||||
print("")
|
||||
stopInput=true
|
||||
if str == "" then
|
||||
printInline("> ")
|
||||
stopInput=false
|
||||
elseif str == "exit()" then
|
||||
break
|
||||
else
|
||||
local func=load(str,"@Lua","t",luaEnv)
|
||||
local ok,err = xpcall(func, debug.traceback)
|
||||
if not ok then
|
||||
print(err)
|
||||
end
|
||||
printInline("\n> ")
|
||||
str=""
|
||||
end
|
||||
str=""
|
||||
else
|
||||
str=str..input
|
||||
printInline(input)
|
||||
end
|
||||
timeout=false
|
||||
else
|
||||
timeout=true
|
||||
end
|
||||
|
||||
if timeout then
|
||||
if stopInput then
|
||||
sleep(.5)
|
||||
else
|
||||
sleep(.05)
|
||||
end
|
||||
end
|
||||
end
|
||||
423
Src/Hyperion-bash/bin/micro
Normal file
423
Src/Hyperion-bash/bin/micro
Normal file
@@ -0,0 +1,423 @@
|
||||
--:Minify:--
|
||||
-- Arrows move cursor Home/End line start/end
|
||||
-- PgUp/PgDn page up/down Backspace delete left
|
||||
-- Ctrl-D/Delete delete right Tab 4 spaces
|
||||
-- Ctrl-W save Ctrl-X save + quit
|
||||
-- Ctrl-P quit Ctrl-K cut line
|
||||
-- Ctrl-U paste Ctrl-F find
|
||||
-- Ctrl-N find next Ctrl-G go to line
|
||||
-- Ctrl-A line start Ctrl-E line end
|
||||
-- Ctrl-B page up Ctrl-L page down
|
||||
|
||||
local args = { ... }
|
||||
|
||||
local function termSize()
|
||||
local s = syscall.devctl(1, "size")
|
||||
return tonumber(s:match("^(%d+)")) or 80,
|
||||
tonumber(s:match(";(%d+)$")) or 24
|
||||
end
|
||||
local function tpos(x,y) syscall.devctl(1,"spos",x,y) end
|
||||
local function tfg(c) syscall.devctl(1,"sfgc",c) end
|
||||
local function tbg(c) syscall.devctl(1,"sbgc",c) end
|
||||
local function twrite(s) if s and s~="" then syscall.write(1,s) end end
|
||||
local function tclear() syscall.devctl(1,"clear") end
|
||||
|
||||
local W, H = termSize()
|
||||
local ROWS = H - 2
|
||||
|
||||
local lines = {""}
|
||||
local cx = 1
|
||||
local cy = 1
|
||||
local scrollY = 0
|
||||
local dirty = true
|
||||
local fname = nil
|
||||
local msg = ""
|
||||
local msgErr = false
|
||||
local clip = nil
|
||||
local sPat = ""
|
||||
local sLine = 0
|
||||
local blinkState = false
|
||||
|
||||
local function absPath(p)
|
||||
if p:sub(1,1) == "/" then return p end
|
||||
local cwd = syscall.getcwd()
|
||||
cwd = cwd:gsub("/+$", "")
|
||||
return cwd .. "/" .. p
|
||||
end
|
||||
|
||||
local function loadFile(path)
|
||||
if not syscall.exists(path) then
|
||||
lines = {""}; msg = "[new file]"; return
|
||||
end
|
||||
local fd = syscall.open(path, "r")
|
||||
local buf = ""
|
||||
while true do
|
||||
local c = syscall.read(fd, 4096)
|
||||
if not c or c == "" then break end
|
||||
buf = buf .. c
|
||||
end
|
||||
syscall.close(fd)
|
||||
lines = {}
|
||||
for ln in (buf.."\n"):gmatch("([^\n]*)\n") do
|
||||
table.insert(lines, ln)
|
||||
end
|
||||
if #lines > 1 and lines[#lines] == "" and buf:sub(-1) == "\n" then
|
||||
table.remove(lines)
|
||||
end
|
||||
if #lines == 0 then lines = {""} end
|
||||
end
|
||||
|
||||
local function saveFile(path)
|
||||
local ok, err = pcall(function()
|
||||
local fd = syscall.open(path, "w")
|
||||
for i, ln in ipairs(lines) do
|
||||
syscall.write(fd, ln)
|
||||
if i < #lines then syscall.write(fd, "\n") end
|
||||
end
|
||||
syscall.write(fd, "\n")
|
||||
syscall.close(fd)
|
||||
end)
|
||||
if ok then
|
||||
msg = "Saved: "..path; msgErr = false
|
||||
else
|
||||
msg = "Save failed: "..tostring(err); msgErr = true
|
||||
end
|
||||
end
|
||||
|
||||
local function wrappedRows(lineStr)
|
||||
return math.max(1, math.ceil(#lineStr / W))
|
||||
end
|
||||
|
||||
local function logicalToScreen(li, col)
|
||||
return math.floor((col - 1) / W)
|
||||
end
|
||||
|
||||
local function buildScreenMap()
|
||||
local map = {}
|
||||
local sr = 0
|
||||
for li = 1, #lines do
|
||||
local len = #lines[li]
|
||||
local nrows = wrappedRows(lines[li])
|
||||
for r = 0, nrows - 1 do
|
||||
sr = sr + 1
|
||||
map[sr] = {li, r * W + 1}
|
||||
end
|
||||
end
|
||||
return map, sr
|
||||
end
|
||||
|
||||
local function cursorScreenRow(map)
|
||||
local offset = logicalToScreen(cy, cx)
|
||||
for sr, entry in ipairs(map) do
|
||||
if entry[1] == cy and math.floor((entry[2]-1)/W) == offset then
|
||||
return sr
|
||||
end
|
||||
end
|
||||
return 1
|
||||
end
|
||||
|
||||
local function clampCx()
|
||||
local m = #lines[cy] + 1
|
||||
if cx > m then cx = m end
|
||||
if cx < 1 then cx = 1 end
|
||||
end
|
||||
|
||||
local function clampScroll(map)
|
||||
local csr = cursorScreenRow(map)
|
||||
if csr - 1 < scrollY then scrollY = csr - 1 end
|
||||
if csr - 1 >= scrollY + ROWS then scrollY = csr - ROWS end
|
||||
if scrollY < 0 then scrollY = 0 end
|
||||
end
|
||||
|
||||
local function pad(s, w)
|
||||
if #s >= w then return s:sub(1, w) end
|
||||
return s .. string.rep(" ", w - #s)
|
||||
end
|
||||
|
||||
local function drawTop()
|
||||
tpos(1,1); tbg(4); tfg(16)
|
||||
local left = " edit" .. (fname and (" - "..fname) or "")
|
||||
if dirty then left = left .. " [+]" end
|
||||
local right = tostring(cy)..","..tostring(cx).." "
|
||||
twrite(pad(left..string.rep(" ", math.max(1, W-#left-#right))..right, W))
|
||||
tbg(16); tfg(1)
|
||||
end
|
||||
|
||||
local function drawBottom()
|
||||
tpos(1, H); tbg(4); tfg(16)
|
||||
if msg ~= "" then
|
||||
if msgErr then tbg(2) end
|
||||
twrite(pad(" "..msg, W))
|
||||
msg = ""; msgErr = false
|
||||
else
|
||||
twrite(pad(" ^W Save ^X Quit+Save ^P Quit ^K Cut ^U Paste ^F Find ^G Go", W))
|
||||
end
|
||||
tbg(16); tfg(1)
|
||||
end
|
||||
|
||||
local function drawLines(map)
|
||||
local curSR = cursorScreenRow(map)
|
||||
local curRowOffset = logicalToScreen(cy, cx)
|
||||
local curColInRow = cx - curRowOffset * W
|
||||
|
||||
for row = 1, ROWS do
|
||||
local sr = scrollY + row
|
||||
tpos(1, row + 1)
|
||||
local entry = map[sr]
|
||||
if entry then
|
||||
local li = entry[1]
|
||||
local startCol = entry[2]
|
||||
local seg = lines[li]:sub(startCol, startCol + W - 1)
|
||||
local isCursorRow = (sr == curSR)
|
||||
|
||||
if isCursorRow then
|
||||
local ci = curColInRow
|
||||
ci = math.min(ci, #seg + 1)
|
||||
local before = seg:sub(1, ci-1)
|
||||
local curCh = ci > #seg and " " or seg:sub(ci, ci)
|
||||
local after = seg:sub(ci+1)
|
||||
|
||||
tfg(1); tbg(16); twrite(before)
|
||||
if blinkState then tfg(16); tbg(1) else tfg(1); tbg(16) end
|
||||
twrite(curCh)
|
||||
tfg(1); tbg(16); twrite(after)
|
||||
local drawn = #before + 1 + #after
|
||||
if drawn < W then twrite(string.rep(" ", W - drawn)) end
|
||||
else
|
||||
if li == sLine and sPat ~= "" and entry[2] == 1 then
|
||||
local s, e = seg:find(sPat)
|
||||
if s then
|
||||
tfg(1); tbg(16); twrite(seg:sub(1,s-1))
|
||||
tfg(16); tbg(3); twrite(seg:sub(s,e))
|
||||
tfg(1); tbg(16); twrite(seg:sub(e+1))
|
||||
twrite(string.rep(" ", W - #seg))
|
||||
else
|
||||
tfg(1); tbg(16); twrite(pad(seg, W))
|
||||
end
|
||||
else
|
||||
tfg(1); tbg(16); twrite(pad(seg, W))
|
||||
end
|
||||
end
|
||||
else
|
||||
tfg(13); tbg(16); twrite(pad("~", W)); tfg(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function redraw()
|
||||
W, H = termSize(); ROWS = H - 2
|
||||
local map = buildScreenMap()
|
||||
clampScroll(map)
|
||||
drawTop()
|
||||
drawLines(map)
|
||||
drawBottom()
|
||||
tpos(1, H)
|
||||
tbg(16); tfg(1)
|
||||
end
|
||||
|
||||
local function prompt(label, default)
|
||||
local inp = default or ""
|
||||
while true do
|
||||
tpos(1, H); tbg(3); tfg(16)
|
||||
twrite(pad(" "..label..inp.." ", W))
|
||||
tbg(16); tfg(1)
|
||||
local key = syscall.read(0)
|
||||
if not key or key == "" then sleep(0.02)
|
||||
elseif key == "\27" then return nil
|
||||
elseif key == "\n" then return inp
|
||||
elseif key == "\b" then if #inp > 0 then inp = inp:sub(1,-2) end
|
||||
else
|
||||
local b = key:byte(1)
|
||||
if b >= 32 and b < 127 then inp = inp..key:sub(1,1) end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function insChar(c)
|
||||
local ln = lines[cy]
|
||||
lines[cy] = ln:sub(1,cx-1)..c..ln:sub(cx)
|
||||
cx = cx+1; dirty = true
|
||||
end
|
||||
|
||||
local function delLeft()
|
||||
if cx > 1 then
|
||||
local ln = lines[cy]
|
||||
lines[cy] = ln:sub(1,cx-2)..ln:sub(cx)
|
||||
cx = cx-1; dirty = true
|
||||
elseif cy > 1 then
|
||||
local above = lines[cy-1]
|
||||
cx = #above+1
|
||||
lines[cy-1] = above..lines[cy]
|
||||
table.remove(lines, cy)
|
||||
cy = cy-1; dirty = true
|
||||
end
|
||||
end
|
||||
|
||||
local function delRight()
|
||||
local ln = lines[cy]
|
||||
if cx <= #ln then
|
||||
lines[cy] = ln:sub(1,cx-1)..ln:sub(cx+1); dirty = true
|
||||
elseif cy < #lines then
|
||||
lines[cy] = ln..lines[cy+1]
|
||||
table.remove(lines, cy+1); dirty = true
|
||||
end
|
||||
end
|
||||
|
||||
local function newline()
|
||||
local ln = lines[cy]
|
||||
local pre = ln:sub(1,cx-1)
|
||||
local post = ln:sub(cx)
|
||||
local ind = pre:match("^(%s*)") or ""
|
||||
lines[cy] = pre
|
||||
table.insert(lines, cy+1, ind..post)
|
||||
cy = cy+1; cx = #ind+1; dirty = true
|
||||
end
|
||||
|
||||
local function cutLine()
|
||||
clip = lines[cy]
|
||||
table.remove(lines, cy)
|
||||
if #lines == 0 then lines = {""} end
|
||||
if cy > #lines then cy = #lines end
|
||||
cx = 1; dirty = true; msg = "Cut"
|
||||
end
|
||||
|
||||
local function pasteLine()
|
||||
if not clip then msg = "Nothing to paste"; return end
|
||||
table.insert(lines, cy, clip)
|
||||
cy = cy+1; cx = 1; dirty = true; msg = "Pasted"
|
||||
end
|
||||
|
||||
local function findNext()
|
||||
if sPat == "" then
|
||||
local p = prompt("Find: ", "")
|
||||
if not p or p == "" then dirty = true; return end
|
||||
sPat = p; sLine = 0
|
||||
end
|
||||
local start = sLine > 0 and sLine or cy
|
||||
for i = 1, #lines do
|
||||
local idx = (start-1+i) % #lines + 1
|
||||
if lines[idx]:find(sPat) then
|
||||
cy = idx; sLine = idx
|
||||
cx = lines[idx]:find(sPat) or 1
|
||||
msg = "Found: line "..idx; dirty = true; return
|
||||
end
|
||||
end
|
||||
msg = "Not found: "..sPat; msgErr = true; dirty = true
|
||||
end
|
||||
|
||||
local function goToLine()
|
||||
local p = prompt("Go to line: ", "")
|
||||
if not p then dirty = true; return end
|
||||
local n = tonumber(p)
|
||||
if not n then msg = "Not a number"; msgErr = true; dirty = true; return end
|
||||
cy = math.max(1, math.min(#lines, math.floor(n)))
|
||||
cx = 1; msg = "Line "..cy; dirty = true
|
||||
end
|
||||
|
||||
local function doSave()
|
||||
if not fname then
|
||||
local p = prompt("Save as: ", "")
|
||||
dirty = true
|
||||
if not p or p == "" then return false end
|
||||
fname = absPath(p)
|
||||
end
|
||||
saveFile(fname); dirty = true; return not msgErr
|
||||
end
|
||||
|
||||
local function moveCursorUp(map)
|
||||
local csr = cursorScreenRow(map)
|
||||
if csr <= 1 then return end
|
||||
local prev = map[csr - 1]
|
||||
if not prev then return end
|
||||
local newLi = prev[1]
|
||||
local newCol = prev[2] + (cx - 1) % W
|
||||
cx = math.min(newCol, #lines[newLi] + 1)
|
||||
cy = newLi
|
||||
end
|
||||
|
||||
local function moveCursorDown(map)
|
||||
local csr = cursorScreenRow(map)
|
||||
local next = map[csr + 1]
|
||||
if not next then return end
|
||||
local newLi = next[1]
|
||||
local newCol = next[2] + (cx - 1) % W
|
||||
cx = math.min(newCol, #lines[newLi] + 1)
|
||||
cy = newLi
|
||||
end
|
||||
|
||||
if args[1] then
|
||||
fname = absPath(args[1])
|
||||
loadFile(fname)
|
||||
end
|
||||
|
||||
tclear()
|
||||
|
||||
local running = true
|
||||
while running do
|
||||
local map = buildScreenMap()
|
||||
|
||||
local key = syscall.read(0)
|
||||
if key and key ~= "" then
|
||||
local b = key:byte(1)
|
||||
if key == "\17" then moveCursorUp(map); dirty=true
|
||||
elseif key == "\18" then moveCursorDown(map); dirty=true
|
||||
elseif key == "\19" then
|
||||
if cx > 1 then cx=cx-1
|
||||
elseif cy > 1 then cy=cy-1; cx=#lines[cy]+1 end
|
||||
dirty=true
|
||||
elseif key == "\20" then
|
||||
if cx <= #lines[cy] then cx=cx+1
|
||||
elseif cy < #lines then cy=cy+1; cx=1 end
|
||||
dirty=true
|
||||
elseif key == "\n" then newline()
|
||||
elseif key == "\b" then delLeft()
|
||||
elseif key == "\t" then for _=1,4 do insChar(" ") end
|
||||
elseif b == 1 then cx=1; dirty=true
|
||||
elseif b == 2 then
|
||||
for _=1,ROWS do moveCursorUp(map) end; dirty=true
|
||||
elseif b == 4 then delRight()
|
||||
elseif b == 5 then cx=#lines[cy]+1; dirty=true
|
||||
elseif b == 6 then
|
||||
local p=prompt("Find: ",sPat); dirty=true
|
||||
if p then sPat=p; sLine=0; findNext() end
|
||||
elseif b == 7 then goToLine()
|
||||
elseif b == 11 then cutLine()
|
||||
elseif b == 12 then
|
||||
for _=1,ROWS do moveCursorDown(map) end; dirty=true
|
||||
elseif b == 14 then
|
||||
if sPat=="" then
|
||||
local p=prompt("Find: ",""); dirty=true
|
||||
if p then sPat=p; sLine=0 end
|
||||
end
|
||||
findNext()
|
||||
elseif b == 16 then
|
||||
if dirty then
|
||||
local p=prompt("Unsaved changes. Quit? [y/N] ","")
|
||||
dirty=true
|
||||
if p and p:lower()=="y" then running=false end
|
||||
else running=false end
|
||||
elseif b == 21 then pasteLine()
|
||||
elseif b == 23 then doSave()
|
||||
elseif b == 24 then doSave(); running=false
|
||||
else
|
||||
if b >= 32 and b < 127 then insChar(key:sub(1,1)) end
|
||||
end
|
||||
end
|
||||
|
||||
local curBlink = (math.floor(syscall.getUptime() / 500) % 2) == 0
|
||||
if curBlink ~= blinkState then
|
||||
blinkState = curBlink
|
||||
dirty = true
|
||||
end
|
||||
|
||||
if dirty then
|
||||
clampCx()
|
||||
redraw()
|
||||
dirty = false
|
||||
end
|
||||
|
||||
sleep(0.05)
|
||||
end
|
||||
|
||||
tclear(); tfg(1); tbg(16); tpos(1,1)
|
||||
print("edit: exited"..(fname and (" - "..fname) or ""))
|
||||
23
Src/Hyperion-bash/bin/mkdir
Normal file
23
Src/Hyperion-bash/bin/mkdir
Normal file
@@ -0,0 +1,23 @@
|
||||
local args = {...}
|
||||
local name = syscall.getTask(syscall.getpid()).name
|
||||
if #args == 0 then
|
||||
print(name..": Missing operand.")
|
||||
return
|
||||
end
|
||||
|
||||
local fs = require("sys.fs")
|
||||
local newDir = args[1]
|
||||
if newDir:sub(1, 1) ~= "/" then
|
||||
newDir = syscall.getcwd().."/"..newDir
|
||||
end
|
||||
|
||||
if newDir:sub(#newDir, #newDir) ~= "/" then
|
||||
newDir = newDir.."/"
|
||||
end
|
||||
|
||||
if fs.isDir(newDir) then
|
||||
print(name..": Cannot create directory '"..args[1].."': Directory already exists.")
|
||||
return
|
||||
end
|
||||
|
||||
fs.mkdir(newDir)
|
||||
152
Src/Hyperion-bash/bin/mount
Normal file
152
Src/Hyperion-bash/bin/mount
Normal file
@@ -0,0 +1,152 @@
|
||||
--:Minify:--
|
||||
-- Usage:
|
||||
-- mount list all current mounts
|
||||
-- mount <id> <mountpoint> mount loop device id at mountpoint
|
||||
-- mount -o loop <src> <dest> attach <src> as loop device and mount at <dest>
|
||||
-- mount --help
|
||||
|
||||
local name = syscall.getTask(syscall.getpid()).name
|
||||
local args, opts = {}, { help=false, o=nil }
|
||||
|
||||
local i = 1
|
||||
local rawArgs = {...}
|
||||
while i <= #rawArgs do
|
||||
local v = rawArgs[i]
|
||||
if v:sub(1,2) == "--" then
|
||||
local o = v:sub(3)
|
||||
if o == "help" then opts.help = true
|
||||
else print(name..": unrecognised option '"..v.."'")
|
||||
print("try '"..name.." --help' for more information.")
|
||||
syscall.exit(1); return end
|
||||
elseif v == "-o" then
|
||||
i = i + 1
|
||||
opts.o = rawArgs[i]
|
||||
elseif v:sub(1,1) == "-" then
|
||||
local rest = v:sub(2)
|
||||
if rest:sub(1,1) == "o" then
|
||||
if #rest > 1 then opts.o = rest:sub(2)
|
||||
else i = i + 1; opts.o = rawArgs[i] end
|
||||
else
|
||||
print(name..": invalid option '"..v.."'")
|
||||
print("try '"..name.." --help' for more information.")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
else
|
||||
table.insert(args, v)
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
if opts.help then
|
||||
print("Usage: "..name)
|
||||
print(" "..name.." <id> <mountpoint>")
|
||||
print(" "..name.." -o loop <source> <mountpoint>")
|
||||
print("")
|
||||
print("Mount a loop device or filesystem.")
|
||||
print("")
|
||||
print(" (no args) list all active mount points")
|
||||
print(" <id> <mountpoint> mount an already-attached loop device")
|
||||
print(" -o loop <src> <dest> attach src as loop device and mount at dest")
|
||||
print(" src can be a directory (bind) or .hfs image")
|
||||
print("")
|
||||
print("Requires root for all operations except listing.")
|
||||
return
|
||||
end
|
||||
|
||||
if #args == 0 and not opts.o then
|
||||
local ok, mounts = pcall(syscall.mounts or function()
|
||||
error("ENOSYS")
|
||||
end)
|
||||
|
||||
local loDevs = {}
|
||||
local lok, ld = pcall(syscall.lolist)
|
||||
if lok then
|
||||
for id, info in pairs(ld) do
|
||||
local path = (type(info)=="table" and info.path) or tostring(info)
|
||||
local mode = (type(info)=="table" and info.mode) or "bind"
|
||||
loDevs[id] = { path=path, mode=mode }
|
||||
end
|
||||
end
|
||||
|
||||
if next(loDevs) == nil then
|
||||
syscall.devctl(1, "sfgc", 14)
|
||||
print("(no loop devices attached)")
|
||||
syscall.devctl(1, "sfgc", 1)
|
||||
return
|
||||
end
|
||||
|
||||
for id, info in pairs(loDevs) do
|
||||
local colour = info.mode == "image" and 5 or 4
|
||||
syscall.devctl(1, "sfgc", colour)
|
||||
printInline(info.mode.." "..id)
|
||||
syscall.devctl(1, "sfgc", 1)
|
||||
print(" on "..info.path)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
if opts.o and opts.o:lower() == "loop" then
|
||||
if #args < 2 then
|
||||
print(name..": -o loop requires <source> and <mountpoint>")
|
||||
print("try '"..name.." --help' for more information.")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
local src = args[1]
|
||||
local dest = args[2]
|
||||
|
||||
if src:sub(1,1) ~= "/" then src = syscall.getcwd().."/"..src end
|
||||
if dest:sub(1,1) ~= "/" then dest = syscall.getcwd().."/"..dest end
|
||||
|
||||
local ok, loopId = pcall(syscall.losetup, src)
|
||||
if not ok then
|
||||
local msg = tostring(loopId)
|
||||
if msg:find("EPERM") then msg = "Permission denied"
|
||||
elseif msg:find("EINVAL") then msg = "'"..args[1].."': not a directory or .hfs image"
|
||||
elseif msg:find("EIO") then msg = "'"..args[1].."': I/O error reading image"
|
||||
end
|
||||
print(name..": losetup: "..msg); syscall.exit(1); return
|
||||
end
|
||||
|
||||
local ok2, merr = pcall(syscall.mount, dest, loopId)
|
||||
if not ok2 then
|
||||
pcall(syscall.lodetach, loopId)
|
||||
local msg = tostring(merr)
|
||||
if msg:find("EPERM") then msg = "Permission denied"
|
||||
elseif msg:find("EBUSY") then msg = "'"..dest.."' is already a mount point"
|
||||
elseif msg:find("ENODEV") then msg = "loop device not found (internal error)"
|
||||
end
|
||||
print(name..": mount: "..msg); syscall.exit(1); return
|
||||
end
|
||||
|
||||
syscall.devctl(1, "sfgc", 10)
|
||||
print(name..": "..loopId.." mounted at "..dest)
|
||||
syscall.devctl(1, "sfgc", 1)
|
||||
return
|
||||
end
|
||||
|
||||
if #args == 2 then
|
||||
local loopId = args[1]
|
||||
local dest = args[2]
|
||||
if dest:sub(1,1) ~= "/" then dest = syscall.getcwd().."/"..dest end
|
||||
|
||||
local ok, err = pcall(syscall.mount, dest, loopId)
|
||||
if not ok then
|
||||
local msg = tostring(err)
|
||||
if msg:find("EPERM") then msg = "Permission denied"
|
||||
elseif msg:find("ENODEV") then msg = "'"..loopId.."': no such device - use losetup first"
|
||||
elseif msg:find("EBUSY") then msg = "'"..dest.."' is already a mount point"
|
||||
elseif msg:find("EINVAL") then msg = "invalid arguments"
|
||||
end
|
||||
print(name..": "..msg); syscall.exit(1); return
|
||||
end
|
||||
|
||||
syscall.devctl(1, "sfgc", 10)
|
||||
print(name..": "..loopId.." mounted at "..dest)
|
||||
syscall.devctl(1, "sfgc", 1)
|
||||
return
|
||||
end
|
||||
|
||||
print(name..": wrong number of arguments")
|
||||
print("try '"..name.." --help' for more information.")
|
||||
syscall.exit(1)
|
||||
80
Src/Hyperion-bash/bin/passwd
Normal file
80
Src/Hyperion-bash/bin/passwd
Normal file
@@ -0,0 +1,80 @@
|
||||
--:Minify:--
|
||||
-- passwd: change a user's password
|
||||
-- Usage: passwd [username] (default: current user)
|
||||
|
||||
local args = {...}
|
||||
local targetName = args[1]
|
||||
|
||||
local currentUid = syscall.getuid()
|
||||
|
||||
local targetUid
|
||||
if targetName then
|
||||
targetUid = syscall.getuid(targetName)
|
||||
if not targetUid then
|
||||
print("passwd: user '" .. targetName .. "' does not exist")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
-- Only root can change another user's password
|
||||
if currentUid ~= 0 and targetUid ~= currentUid then
|
||||
print("passwd: permission denied")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
else
|
||||
targetUid = currentUid
|
||||
targetName = syscall.getUsername(currentUid) or tostring(currentUid)
|
||||
end
|
||||
|
||||
-- Non-root must verify their current password first
|
||||
if currentUid ~= 0 then
|
||||
printInline("Current password: ")
|
||||
local cur = ""
|
||||
while true do
|
||||
local ch = syscall.read(0)
|
||||
if not ch or ch == "" then
|
||||
elseif ch == "\n" then syscall.write(1,"\n"); break
|
||||
elseif ch == "\b" then
|
||||
if #cur > 0 then cur=cur:sub(1,-2); syscall.write(1,"\b \b") end
|
||||
else cur=cur..ch; syscall.write(1,"*") end
|
||||
end
|
||||
local ok, err = syscall.elevate(targetName, cur)
|
||||
if not ok then
|
||||
sleep(1)
|
||||
print("passwd: authentication failure")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
end
|
||||
|
||||
printInline("New password: ")
|
||||
local pw1 = ""
|
||||
while true do
|
||||
local ch = syscall.read(0)
|
||||
if not ch or ch == "" then
|
||||
elseif ch == "\n" then syscall.write(1,"\n"); break
|
||||
elseif ch == "\b" then
|
||||
if #pw1 > 0 then pw1=pw1:sub(1,-2); syscall.write(1,"\b \b") end
|
||||
else pw1=pw1..ch; syscall.write(1,"*") end
|
||||
end
|
||||
|
||||
printInline("Confirm password: ")
|
||||
local pw2 = ""
|
||||
while true do
|
||||
local ch = syscall.read(0)
|
||||
if not ch or ch == "" then
|
||||
elseif ch == "\n" then syscall.write(1,"\n"); break
|
||||
elseif ch == "\b" then
|
||||
if #pw2 > 0 then pw2=pw2:sub(1,-2); syscall.write(1,"\b \b") end
|
||||
else pw2=pw2..ch; syscall.write(1,"*") end
|
||||
end
|
||||
|
||||
if pw1 ~= pw2 then
|
||||
print("passwd: passwords do not match")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
local ok, err = syscall.setpassword(targetUid, pw1)
|
||||
if not ok then
|
||||
print("passwd: " .. tostring(err))
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
print("passwd: password updated for '" .. targetName .. "'")
|
||||
4
Src/Hyperion-bash/bin/ps
Normal file
4
Src/Hyperion-bash/bin/ps
Normal file
@@ -0,0 +1,4 @@
|
||||
for i,v in ipairs(syscall.getTasks()) do
|
||||
local task = syscall.getTask(v)
|
||||
print(task.pid,task.username,task.name,task.status)
|
||||
end
|
||||
1
Src/Hyperion-bash/bin/pwd
Normal file
1
Src/Hyperion-bash/bin/pwd
Normal file
@@ -0,0 +1 @@
|
||||
print(syscall.getcwd())
|
||||
82
Src/Hyperion-bash/bin/readlink
Normal file
82
Src/Hyperion-bash/bin/readlink
Normal file
@@ -0,0 +1,82 @@
|
||||
--:Minify:--
|
||||
local name = syscall.getTask(syscall.getpid()).name
|
||||
local cloptions = { n = false, f = false, e = false, help = false }
|
||||
local args = {}
|
||||
|
||||
for _, v in ipairs({ ... }) do
|
||||
if v:sub(1, 2) == "--" then
|
||||
local opt = v:sub(3)
|
||||
if cloptions[opt] == nil then
|
||||
print(name .. ": unrecognized option '" .. v .. "'")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
cloptions[opt] = true
|
||||
elseif v:sub(1, 1) == "-" then
|
||||
for i = 2, #v do
|
||||
local opt = v:sub(i, i)
|
||||
if cloptions[opt] == nil then
|
||||
print(name .. ": invalid option '-" .. opt .. "'")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
cloptions[opt] = true
|
||||
end
|
||||
else
|
||||
table.insert(args, v)
|
||||
end
|
||||
end
|
||||
|
||||
if cloptions.help then
|
||||
print("Usage: " .. name .. " [OPTION]... FILE...")
|
||||
print("Print the resolved target of symbolic links.")
|
||||
print("")
|
||||
print("Options:")
|
||||
print(" -f canonicalize: follow every symlink; last component need not exist")
|
||||
print(" -e like -f but all components must exist")
|
||||
print(" -n do not output trailing newline")
|
||||
print(" --help display this help and exit")
|
||||
return
|
||||
end
|
||||
|
||||
if #args == 0 then
|
||||
print(name .. ": missing operand")
|
||||
print("try '" .. name .. " --help' for more information.")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
local function absPath(p)
|
||||
if p:sub(1,1) ~= "/" then
|
||||
local d = syscall.getcwd()
|
||||
if d:sub(-1) ~= "/" then d = d .. "/" end
|
||||
p = d .. p
|
||||
end
|
||||
return p
|
||||
end
|
||||
|
||||
local anyErr = false
|
||||
for _, path in ipairs(args) do
|
||||
path = absPath(path)
|
||||
|
||||
if cloptions.f or cloptions.e then
|
||||
local ok, stat = pcall(syscall.stat, path)
|
||||
if not ok then
|
||||
if cloptions.e then
|
||||
print(name .. ": " .. path .. ": " .. tostring(stat))
|
||||
anyErr = true
|
||||
else
|
||||
if not cloptions.n then print(path) else printInline(path) end
|
||||
end
|
||||
else
|
||||
if not cloptions.n then print(path) else printInline(path) end
|
||||
end
|
||||
else
|
||||
local ok, target = pcall(syscall.readlink, path)
|
||||
if not ok then
|
||||
print(name .. ": " .. path .. ": " .. tostring(target))
|
||||
anyErr = true
|
||||
else
|
||||
if not cloptions.n then print(target) else printInline(target) end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if anyErr then syscall.exit(1) end
|
||||
429
Src/Hyperion-bash/bin/sed
Normal file
429
Src/Hyperion-bash/bin/sed
Normal file
@@ -0,0 +1,429 @@
|
||||
--:Minify:--
|
||||
-- Supports: s/pat/repl/[gip], d, p, q, =, addr1[,addr2]cmd
|
||||
-- Addressing: line numbers, $, /regex/
|
||||
-- Flags: -n (silent), -e script, -i (in-place)
|
||||
|
||||
local name = syscall.getTask(syscall.getpid()).name
|
||||
|
||||
local scripts = {}
|
||||
local files = {}
|
||||
local silent = false
|
||||
local inplace = false
|
||||
local args = { ... }
|
||||
local i = 1
|
||||
|
||||
while i <= #args do
|
||||
local a = args[i]
|
||||
if a == "-n" then
|
||||
silent = true
|
||||
elseif a == "-i" then
|
||||
inplace = true
|
||||
elseif a == "-e" then
|
||||
i = i + 1
|
||||
if not args[i] then
|
||||
print(name .. ": option -e requires an argument"); syscall.exit(1); return
|
||||
end
|
||||
table.insert(scripts, args[i])
|
||||
elseif a:sub(1,2) == "-e" then
|
||||
table.insert(scripts, a:sub(3))
|
||||
elseif a == "--help" then
|
||||
print("Usage: " .. name .. " [OPTION]... SCRIPT [FILE...]")
|
||||
print(" " .. name .. " [OPTION]... -e SCRIPT... [FILE...]")
|
||||
print("Stream editor. Reads FILE(s) (or stdin) line by line,")
|
||||
print("applies SCRIPT, and writes results to stdout.")
|
||||
print("")
|
||||
print("Commands:")
|
||||
print(" s/REGEX/REPL/[flags] substitute (flags: g global, i ignore-case, p print)")
|
||||
print(" d delete line (skip to next)")
|
||||
print(" p print current line")
|
||||
print(" q quit")
|
||||
print(" = print current line number")
|
||||
print(" y/src/dst/ transliterate characters")
|
||||
print("")
|
||||
print("Addressing (prefix any command):")
|
||||
print(" N line number N")
|
||||
print(" $ last line")
|
||||
print(" /REGEX/ lines matching regex")
|
||||
print(" N,M line range")
|
||||
print(" N,/REGEX/ from line N until regex match")
|
||||
print("")
|
||||
print("Options:")
|
||||
print(" -n suppress default output")
|
||||
print(" -e SCRIPT add script expression")
|
||||
print(" -i edit file in-place")
|
||||
print(" --help display this help and exit")
|
||||
return
|
||||
elseif a:sub(1,1) == "-" then
|
||||
print(name .. ": unknown option: " .. a)
|
||||
syscall.exit(1); return
|
||||
else
|
||||
if #scripts == 0 then
|
||||
table.insert(scripts, a)
|
||||
else
|
||||
table.insert(files, a)
|
||||
end
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
if #scripts == 0 then
|
||||
print(name .. ": no script specified"); syscall.exit(1); return
|
||||
end
|
||||
|
||||
local script = table.concat(scripts, "\n")
|
||||
|
||||
local function patEscape(s)
|
||||
return s:gsub("([%(%)%.%%%+%-%*%?%[%^%$])", "%%%1")
|
||||
end
|
||||
|
||||
local function sedPatToLua(pat, icase)
|
||||
pat = pat:gsub("\\%(", "("):gsub("\\%)", ")")
|
||||
pat = pat:gsub("\\1", "%%1"):gsub("\\2", "%%2")
|
||||
return pat
|
||||
end
|
||||
|
||||
local function parseDelim(s, pos, delim)
|
||||
local out = {}
|
||||
while pos <= #s do
|
||||
local c = s:sub(pos, pos)
|
||||
if c == "\\" and pos < #s then
|
||||
pos = pos + 1
|
||||
local nc = s:sub(pos, pos)
|
||||
if nc == delim then
|
||||
table.insert(out, delim)
|
||||
elseif nc == "n" then
|
||||
table.insert(out, "\n")
|
||||
else
|
||||
table.insert(out, "\\" .. nc)
|
||||
end
|
||||
elseif c == delim then
|
||||
return table.concat(out), pos + 1
|
||||
else
|
||||
table.insert(out, c)
|
||||
end
|
||||
pos = pos + 1
|
||||
end
|
||||
return table.concat(out), pos
|
||||
end
|
||||
|
||||
local function parseAddr(s, pos)
|
||||
local c = s:sub(pos, pos)
|
||||
if c == "" then return nil, pos end
|
||||
if c:match("%d") then
|
||||
local numstr = s:match("^(%d+)", pos)
|
||||
return { type="line", n=tonumber(numstr) }, pos + #numstr
|
||||
elseif c == "$" then
|
||||
return { type="last" }, pos + 1
|
||||
elseif c == "/" then
|
||||
local pat, npos = parseDelim(s, pos + 1, "/")
|
||||
return { type="regex", pat=pat }, npos
|
||||
end
|
||||
return nil, pos
|
||||
end
|
||||
|
||||
local function parseCommands(src)
|
||||
local cmds = {}
|
||||
local pos = 1
|
||||
local len = #src
|
||||
|
||||
local function skip()
|
||||
while pos <= len and (src:sub(pos,pos) == " " or src:sub(pos,pos) == "\t") do
|
||||
pos = pos + 1
|
||||
end
|
||||
end
|
||||
|
||||
while pos <= len do
|
||||
skip()
|
||||
if pos > len then break end
|
||||
|
||||
local c = src:sub(pos, pos)
|
||||
if c == "\n" or c == ";" then
|
||||
pos = pos + 1
|
||||
goto continue
|
||||
end
|
||||
if c == "#" then
|
||||
while pos <= len and src:sub(pos,pos) ~= "\n" do pos = pos + 1 end
|
||||
goto continue
|
||||
end
|
||||
|
||||
local addr1, addr2
|
||||
addr1, pos = parseAddr(src, pos)
|
||||
skip()
|
||||
if addr1 and pos <= len and src:sub(pos,pos) == "," then
|
||||
pos = pos + 1
|
||||
skip()
|
||||
addr2, pos = parseAddr(src, pos)
|
||||
end
|
||||
skip()
|
||||
|
||||
if pos > len then break end
|
||||
local cmd = src:sub(pos, pos)
|
||||
pos = pos + 1
|
||||
|
||||
if cmd == "s" then
|
||||
local delim = src:sub(pos, pos); pos = pos + 1
|
||||
local pat, p1 = parseDelim(src, pos, delim); pos = p1
|
||||
local repl, p2 = parseDelim(src, pos, delim); pos = p2
|
||||
local flags = ""
|
||||
while pos <= len and src:sub(pos,pos):match("[giIp]") do
|
||||
flags = flags .. src:sub(pos,pos); pos = pos + 1
|
||||
end
|
||||
table.insert(cmds, { addr1=addr1, addr2=addr2, cmd="s",
|
||||
pat=pat, repl=repl, flags=flags })
|
||||
|
||||
elseif cmd == "y" then
|
||||
local delim = src:sub(pos, pos); pos = pos + 1
|
||||
local srcch, p1 = parseDelim(src, pos, delim); pos = p1
|
||||
local dstch, p2 = parseDelim(src, pos, delim); pos = p2
|
||||
table.insert(cmds, { addr1=addr1, addr2=addr2, cmd="y",
|
||||
src=srcch, dst=dstch })
|
||||
|
||||
elseif cmd == "d" or cmd == "p" or cmd == "q" or cmd == "=" then
|
||||
table.insert(cmds, { addr1=addr1, addr2=addr2, cmd=cmd })
|
||||
|
||||
elseif cmd == "{" then
|
||||
local depth = 1
|
||||
local start = pos
|
||||
while pos <= len and depth > 0 do
|
||||
local ch = src:sub(pos,pos)
|
||||
if ch == "{" then depth = depth + 1
|
||||
elseif ch == "}" then depth = depth - 1 end
|
||||
pos = pos + 1
|
||||
end
|
||||
local inner = src:sub(start, pos - 2)
|
||||
local innerCmds = parseCommands(inner)
|
||||
for _, ic in ipairs(innerCmds) do
|
||||
ic.addr1 = ic.addr1 or addr1
|
||||
ic.addr2 = ic.addr2 or addr2
|
||||
end
|
||||
for _, ic in ipairs(innerCmds) do
|
||||
table.insert(cmds, ic)
|
||||
end
|
||||
|
||||
elseif cmd == "\n" or cmd == ";" then
|
||||
else
|
||||
end
|
||||
|
||||
::continue::
|
||||
end
|
||||
|
||||
return cmds
|
||||
end
|
||||
|
||||
local cmds = parseCommands(script)
|
||||
|
||||
local inRange = {}
|
||||
|
||||
local function addrMatch(cmd, lineNum, line, isLast, ci)
|
||||
local a1 = cmd.addr1
|
||||
local a2 = cmd.addr2
|
||||
|
||||
if not a1 then return true end
|
||||
|
||||
local function matchOne(addr, ln, l)
|
||||
if addr.type == "line" then return ln == addr.n
|
||||
elseif addr.type == "last" then return isLast
|
||||
elseif addr.type == "regex" then return l:find(sedPatToLua(addr.pat)) ~= nil
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
if not a2 then
|
||||
return matchOne(a1, lineNum, line)
|
||||
end
|
||||
|
||||
if inRange[ci] then
|
||||
local endMatch
|
||||
if a2.type == "line" then endMatch = (lineNum >= a2.n)
|
||||
elseif a2.type == "last" then endMatch = isLast
|
||||
elseif a2.type == "regex" then endMatch = (line:find(sedPatToLua(a2.pat)) ~= nil)
|
||||
end
|
||||
if endMatch then inRange[ci] = false end
|
||||
return true
|
||||
else
|
||||
if matchOne(a1, lineNum, line) then
|
||||
if a2.type == "line" and a2.n <= lineNum then
|
||||
else
|
||||
inRange[ci] = true
|
||||
end
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
local function doSubst(line, pat, repl, flags)
|
||||
local global = flags:find("g") ~= nil
|
||||
local icase = flags:find("[iI]") ~= nil
|
||||
local luaPat = sedPatToLua(pat, icase)
|
||||
|
||||
local function buildRepl(whole, ...)
|
||||
local caps = { ... }
|
||||
local out = {}
|
||||
local rp = repl
|
||||
local ri = 1
|
||||
while ri <= #rp do
|
||||
local rc = rp:sub(ri, ri)
|
||||
if rc == "&" then
|
||||
table.insert(out, whole)
|
||||
elseif rc == "\\" and ri < #rp then
|
||||
ri = ri + 1
|
||||
local nc = rp:sub(ri, ri)
|
||||
if nc:match("%d") then
|
||||
local idx = tonumber(nc)
|
||||
table.insert(out, caps[idx] or "")
|
||||
elseif nc == "n" then
|
||||
table.insert(out, "\n")
|
||||
else
|
||||
table.insert(out, nc)
|
||||
end
|
||||
else
|
||||
table.insert(out, rc)
|
||||
end
|
||||
ri = ri + 1
|
||||
end
|
||||
return table.concat(out)
|
||||
end
|
||||
|
||||
local result
|
||||
local changed = false
|
||||
if global then
|
||||
result = line:gsub(luaPat, buildRepl)
|
||||
changed = (result ~= line)
|
||||
else
|
||||
local s, e, whole
|
||||
local parts = { line:find(luaPat) }
|
||||
if parts[1] then
|
||||
s = parts[1]; e = parts[2]
|
||||
local caps = {}
|
||||
for ci = 3, #parts do caps[#caps+1] = parts[ci] end
|
||||
local wmatch = line:sub(s, e)
|
||||
local replStr = buildRepl(wmatch, table.unpack(caps))
|
||||
result = line:sub(1, s-1) .. replStr .. line:sub(e+1)
|
||||
changed = true
|
||||
else
|
||||
result = line
|
||||
end
|
||||
end
|
||||
return result, changed
|
||||
end
|
||||
|
||||
local function doTranslit(line, src, dst)
|
||||
local out = {}
|
||||
for ci = 1, #line do
|
||||
local c = line:sub(ci, ci)
|
||||
local idx = src:find(c, 1, true)
|
||||
if idx and idx <= #dst then
|
||||
table.insert(out, dst:sub(idx, idx))
|
||||
else
|
||||
table.insert(out, c)
|
||||
end
|
||||
end
|
||||
return table.concat(out)
|
||||
end
|
||||
|
||||
local function processLines(lines, outputLines)
|
||||
local total = #lines
|
||||
for lineNum, line in ipairs(lines) do
|
||||
local isLast = (lineNum == total)
|
||||
local deleted = false
|
||||
local printed = false
|
||||
local quit = false
|
||||
|
||||
local bare = line:gsub("\n$", "")
|
||||
|
||||
for ci, cmd in ipairs(cmds) do
|
||||
if addrMatch(cmd, lineNum, bare, isLast, ci) then
|
||||
if cmd.cmd == "d" then
|
||||
deleted = true; break
|
||||
|
||||
elseif cmd.cmd == "p" then
|
||||
table.insert(outputLines, bare)
|
||||
|
||||
elseif cmd.cmd == "=" then
|
||||
table.insert(outputLines, tostring(lineNum))
|
||||
|
||||
elseif cmd.cmd == "q" then
|
||||
if not silent then table.insert(outputLines, bare) end
|
||||
quit = true; break
|
||||
|
||||
elseif cmd.cmd == "s" then
|
||||
local newLine, changed = doSubst(bare, cmd.pat, cmd.repl, cmd.flags)
|
||||
bare = newLine
|
||||
if changed and cmd.flags:find("p") then
|
||||
table.insert(outputLines, bare)
|
||||
end
|
||||
|
||||
elseif cmd.cmd == "y" then
|
||||
bare = doTranslit(bare, cmd.src, cmd.dst)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if quit then break end
|
||||
if not deleted and not silent then
|
||||
table.insert(outputLines, bare)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function readLines(fd)
|
||||
local lines = {}
|
||||
local buf = ""
|
||||
while true do
|
||||
local chunk = syscall.read(fd, 1024)
|
||||
if not chunk or chunk == "" then break end
|
||||
buf = buf .. chunk
|
||||
end
|
||||
for line in (buf .. "\n"):gmatch("([^\n]*)\n") do
|
||||
table.insert(lines, line)
|
||||
end
|
||||
if buf ~= "" and buf:sub(-1) ~= "\n" and lines[#lines] == "" then
|
||||
table.remove(lines)
|
||||
end
|
||||
return lines
|
||||
end
|
||||
|
||||
local function runOnFile(path)
|
||||
local fd
|
||||
if path then
|
||||
local ok, err = pcall(function() fd = syscall.open(path, "r") end)
|
||||
if not ok then
|
||||
print(name .. ": " .. path .. ": " .. tostring(err))
|
||||
return false
|
||||
end
|
||||
else
|
||||
fd = 0
|
||||
end
|
||||
|
||||
local lines = readLines(fd)
|
||||
if path then syscall.close(fd) end
|
||||
|
||||
inRange = {}
|
||||
|
||||
local outputLines = {}
|
||||
processLines(lines, outputLines)
|
||||
|
||||
if inplace and path then
|
||||
local wfd = syscall.open(path, "w")
|
||||
for _, ol in ipairs(outputLines) do
|
||||
syscall.write(wfd, ol .. "\n")
|
||||
end
|
||||
syscall.close(wfd)
|
||||
else
|
||||
for _, ol in ipairs(outputLines) do
|
||||
print(ol)
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
if #files == 0 then
|
||||
runOnFile(nil)
|
||||
else
|
||||
for _, f in ipairs(files) do
|
||||
local absf = f
|
||||
if absf:sub(1,1) ~= "/" then absf = syscall.getcwd() .. "/" .. f end
|
||||
runOnFile(absf)
|
||||
end
|
||||
end
|
||||
151
Src/Hyperion-bash/bin/socktest
Normal file
151
Src/Hyperion-bash/bin/socktest
Normal file
@@ -0,0 +1,151 @@
|
||||
--:Minify:--
|
||||
local args = { ... }
|
||||
local target = args[1] or "http://example.com"
|
||||
|
||||
local function pass(msg) syscall.devctl(1,"sfgc",10); print(" PASS " .. msg); syscall.devctl(1,"sfgc",1) end
|
||||
local function fail(msg) syscall.devctl(1,"sfgc",2); print(" FAIL " .. msg); syscall.devctl(1,"sfgc",1) end
|
||||
local function info(msg) syscall.devctl(1,"sfgc",14); print(" .... " .. msg); syscall.devctl(1,"sfgc",1) end
|
||||
local function head(msg) syscall.devctl(1,"sfgc",4); print("\n" .. msg); syscall.devctl(1,"sfgc",1) end
|
||||
|
||||
local passed, failed = 0, 0
|
||||
local function check(name, ok, err)
|
||||
if ok then passed = passed + 1; pass(name)
|
||||
else failed = failed + 1; fail(name .. " - " .. tostring(err)) end
|
||||
end
|
||||
|
||||
head("[ 1 ] socket() creation")
|
||||
do
|
||||
local ok, fd = pcall(syscall.socket, "inet", "stream")
|
||||
check("socket(inet, stream) returns fd", ok and type(fd) == "number", fd)
|
||||
|
||||
if ok then
|
||||
local cok = pcall(syscall.close, fd)
|
||||
check("close() on socket fd", cok, "close failed")
|
||||
end
|
||||
|
||||
local ok2, fd2 = pcall(syscall.socket, "unix", "stream")
|
||||
check("socket(unix, stream) returns fd", ok2 and type(fd2) == "number", fd2)
|
||||
if ok2 then pcall(syscall.close, fd2) end
|
||||
|
||||
local ok3 = pcall(syscall.socket, "ax25", "stream")
|
||||
check("socket(ax25) returns EAFNOSUPPORT", not ok3, "should have errored")
|
||||
end
|
||||
|
||||
head("[ 2 ] connect() to " .. target)
|
||||
local sockfd
|
||||
do
|
||||
local ok, fd = pcall(syscall.socket, "inet", "stream")
|
||||
check("socket() before connect", ok, fd)
|
||||
|
||||
if ok then
|
||||
sockfd = fd
|
||||
local cok, cerr = pcall(syscall.connect, fd, target)
|
||||
check("connect(" .. target .. ")", cok, cerr)
|
||||
end
|
||||
end
|
||||
|
||||
head("[ 3 ] send() HTTP GET via socket")
|
||||
do
|
||||
if sockfd then
|
||||
local sok, serr = pcall(syscall.send, sockfd, "")
|
||||
check("send() does not error", sok, serr)
|
||||
else
|
||||
check("send() skipped (no socket)", false, "socket creation failed")
|
||||
end
|
||||
end
|
||||
|
||||
head("[ 4 ] recv() reads HTTP response")
|
||||
do
|
||||
if sockfd then
|
||||
info("waiting for response (recv blocks up to 10s)...")
|
||||
local ok, body = pcall(syscall.recv, sockfd, 65536)
|
||||
check("recv() returns non-empty body", ok and body and #body > 0,
|
||||
ok and "empty response" or tostring(body))
|
||||
if ok and body and #body > 0 then
|
||||
info("received " .. #body .. " bytes")
|
||||
local preview = body:sub(1, 120):gsub("\r", ""):gsub("\n", " ")
|
||||
info("preview: " .. preview)
|
||||
end
|
||||
pcall(syscall.close, sockfd)
|
||||
sockfd = nil
|
||||
else
|
||||
check("recv() skipped (no socket)", false, "socket creation failed")
|
||||
end
|
||||
end
|
||||
|
||||
head("[ 5 ] httpget() convenience wrapper")
|
||||
do
|
||||
info("GET " .. target .. " ...")
|
||||
local ok, body = pcall(syscall.httpget, target)
|
||||
check("httpget() succeeds", ok, body)
|
||||
if ok then
|
||||
check("httpget() returns non-empty string", type(body) == "string" and #body > 0, "empty")
|
||||
if type(body) == "string" and #body > 0 then
|
||||
info("received " .. #body .. " bytes")
|
||||
local preview = body:sub(1, 120):gsub("\r", ""):gsub("\n", " ")
|
||||
info("preview: " .. preview)
|
||||
local hasHtml = body:lower():find("<html") ~= nil
|
||||
or body:lower():find("<!doctype") ~= nil
|
||||
or body:find("{") ~= nil
|
||||
or body:find("HTTP") ~= nil
|
||||
check("body looks like HTTP content", hasHtml, "no recognisable content markers")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
head("[ 6 ] UNIX socket loopback IPC")
|
||||
do
|
||||
local sockPath = "/tmp/socktest.sock"
|
||||
pcall(syscall.remove, sockPath)
|
||||
|
||||
local sok, sfd = pcall(syscall.socket, "unix", "stream")
|
||||
check("server socket(unix,stream)", sok, sfd)
|
||||
|
||||
if sok then
|
||||
local bok = pcall(syscall.bind, sfd, sockPath)
|
||||
check("bind(" .. sockPath .. ")", bok, "bind failed")
|
||||
|
||||
local lok = pcall(syscall.listen, sfd, 1)
|
||||
check("listen()", lok, "listen failed")
|
||||
|
||||
local cok, cfd = pcall(syscall.socket, "unix", "stream")
|
||||
check("client socket(unix,stream)", cok, cfd)
|
||||
|
||||
if cok then
|
||||
local connok = pcall(syscall.connect, cfd, sockPath)
|
||||
check("client connect(" .. sockPath .. ")", connok, "connect failed")
|
||||
|
||||
local aok, afd = pcall(syscall.accept, sfd)
|
||||
check("accept() returns client fd", aok, afd)
|
||||
|
||||
if connok and aok then
|
||||
local sendok = pcall(syscall.send, cfd, "hello hyperion")
|
||||
check("send() from client", sendok, "send failed")
|
||||
|
||||
local rok, data = pcall(syscall.recv, afd, 1024)
|
||||
check("recv() on server side", rok and data == "hello hyperion",
|
||||
rok and ("got: " .. tostring(data)) or tostring(data))
|
||||
|
||||
local repok = pcall(syscall.send, afd, "hello back")
|
||||
check("send() reply from server", repok, "send failed")
|
||||
|
||||
local rok2, data2 = pcall(syscall.recv, cfd, 1024)
|
||||
check("recv() reply on client", rok2 and data2 == "hello back",
|
||||
rok2 and ("got: " .. tostring(data2)) or tostring(data2))
|
||||
|
||||
pcall(syscall.close, afd)
|
||||
end
|
||||
|
||||
pcall(syscall.close, cfd)
|
||||
end
|
||||
|
||||
pcall(syscall.close, sfd)
|
||||
pcall(syscall.remove, sockPath)
|
||||
end
|
||||
end
|
||||
|
||||
print("")
|
||||
syscall.devctl(1,"sfgc", failed == 0 and 10 or 2)
|
||||
print(string.format("Results: %d passed, %d failed", passed, failed))
|
||||
syscall.devctl(1,"sfgc",1)
|
||||
if failed > 0 then syscall.exit(1) end
|
||||
56
Src/Hyperion-bash/bin/su
Normal file
56
Src/Hyperion-bash/bin/su
Normal file
@@ -0,0 +1,56 @@
|
||||
--:Minify:--
|
||||
local targetUser = ({ ... })[1] or "root"
|
||||
local currentUid = syscall.getuid()
|
||||
local targetUid = syscall.getuidbyname(targetUser)
|
||||
|
||||
if not targetUid then
|
||||
print("su: user '" .. targetUser .. "' does not exist")
|
||||
syscall.exit(1)
|
||||
return
|
||||
end
|
||||
|
||||
if currentUid ~= 0 then
|
||||
printInline("Password: ")
|
||||
local pw = ""
|
||||
while true do
|
||||
local ch = syscall.read(0)
|
||||
if not ch or ch == "" then
|
||||
elseif ch == "\n" then
|
||||
syscall.write(1, "\n")
|
||||
break
|
||||
elseif ch == "\b" then
|
||||
if #pw > 0 then pw = pw:sub(1, -2); syscall.write(1, "\b \b") end
|
||||
else
|
||||
pw = pw .. ch; syscall.write(1, "*")
|
||||
end
|
||||
end
|
||||
|
||||
local ok, err = syscall.elevate(targetUser, pw)
|
||||
if not ok then
|
||||
sleep(1)
|
||||
print("su: Authentication failure")
|
||||
syscall.exit(1)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
syscall.setuid(targetUid)
|
||||
|
||||
local pwent = syscall.getpasswd(targetUid)
|
||||
local shell = (pwent and pwent.shell) or "/bin/hysh"
|
||||
local homedir = (pwent and pwent.homedir) or "/"
|
||||
|
||||
local ok_cd, err_cd = pcall(syscall.chdir, homedir)
|
||||
if not ok_cd then
|
||||
homedir = "/"
|
||||
syscall.chdir(homedir)
|
||||
end
|
||||
syscall.setEnviron("HOME", homedir)
|
||||
syscall.setEnviron("USER", targetUser)
|
||||
syscall.setEnviron("SHELL", shell)
|
||||
|
||||
local ok, err = pcall(syscall.exec, shell)
|
||||
if not ok then
|
||||
print("su: cannot exec shell '" .. shell .. "': " .. tostring(err))
|
||||
syscall.exit(1)
|
||||
end
|
||||
110
Src/Hyperion-bash/bin/sudo
Normal file
110
Src/Hyperion-bash/bin/sudo
Normal file
@@ -0,0 +1,110 @@
|
||||
--:Minify:--
|
||||
local fs = require("sys.fs")
|
||||
|
||||
local cmdArgs = {...}
|
||||
local targetUser = "root"
|
||||
local i = 1
|
||||
|
||||
if cmdArgs[i] == "-u" then
|
||||
i = i + 1
|
||||
local uarg = cmdArgs[i] or "root"
|
||||
local numUid = tonumber(uarg)
|
||||
if numUid then
|
||||
local pwent = syscall.getpasswd(numUid)
|
||||
targetUser = (pwent and pwent.username) or uarg
|
||||
else
|
||||
targetUser = uarg
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
local cmd = cmdArgs[i]
|
||||
if not cmd or cmd == "" then
|
||||
print("usage: sudo [-u user] <command> [args...]")
|
||||
syscall.exit(1)
|
||||
return
|
||||
end
|
||||
|
||||
local restArgs = {}
|
||||
for j = i + 1, #cmdArgs do restArgs[#restArgs + 1] = cmdArgs[j] end
|
||||
|
||||
local currentUid = syscall.getuid()
|
||||
local currentUser = syscall.getUsername(currentUid) or tostring(currentUid)
|
||||
|
||||
local targetUid = syscall.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
|
||||
5
Src/Hyperion-bash/bin/sysdump
Normal file
5
Src/Hyperion-bash/bin/sysdump
Normal file
@@ -0,0 +1,5 @@
|
||||
local syscalls=syscall.sysdump()
|
||||
for i=1, #syscalls do
|
||||
print(syscalls[i])
|
||||
end
|
||||
print("Total # of syscalls: "..tostring(#syscalls))
|
||||
111
Src/Hyperion-bash/bin/umount
Normal file
111
Src/Hyperion-bash/bin/umount
Normal file
@@ -0,0 +1,111 @@
|
||||
--:Minify:--
|
||||
-- Usage:
|
||||
-- umount <mountpoint> unmount; auto-detach loop device if one is found
|
||||
-- umount -l <id> detach loop device without unmounting (force)
|
||||
-- umount --no-detach <mpt> unmount but leave loop device attached
|
||||
-- umount --help
|
||||
|
||||
local name = syscall.getTask(syscall.getpid()).name
|
||||
local args, opts = {}, { l=false, ["no-detach"]=false, help=false }
|
||||
|
||||
for _, v in ipairs({...}) do
|
||||
if v:sub(1,2) == "--" then
|
||||
local o = v:sub(3)
|
||||
if opts[o] ~= nil then opts[o] = true
|
||||
else print(name..": unrecognised option '"..v.."'")
|
||||
print("try '"..name.." --help' for more information.")
|
||||
syscall.exit(1); return end
|
||||
elseif v:sub(1,1) == "-" then
|
||||
for i = 2, #v do
|
||||
local c = v:sub(i,i)
|
||||
if opts[c] ~= nil then opts[c] = true
|
||||
else print(name..": invalid option '-"..c.."'")
|
||||
print("try '"..name.." --help' for more information.")
|
||||
syscall.exit(1); return end
|
||||
end
|
||||
else
|
||||
table.insert(args, v)
|
||||
end
|
||||
end
|
||||
|
||||
if opts.help then
|
||||
print("Usage: "..name.." <mountpoint>")
|
||||
print(" "..name.." --no-detach <mountpoint>")
|
||||
print(" "..name.." -l <loopid>")
|
||||
print("")
|
||||
print("Unmount a filesystem mounted at <mountpoint>.")
|
||||
print("")
|
||||
print(" <mountpoint> unmount and auto-detach any loop device")
|
||||
print(" --no-detach unmount but keep the loop device attached")
|
||||
print(" -l <loopid> forcibly detach a loop device (no unmount)")
|
||||
print("")
|
||||
print("Requires root.")
|
||||
return
|
||||
end
|
||||
|
||||
if opts.l then
|
||||
if #args < 1 then
|
||||
print(name..": -l requires a loop device id")
|
||||
print("try '"..name.." --help' for more information.")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
local id = args[1]
|
||||
local ok, err = pcall(syscall.lodetach, id)
|
||||
if not ok then
|
||||
local msg = tostring(err)
|
||||
if msg:find("EPERM") then msg = "Permission denied"
|
||||
elseif msg:find("ENXIO") then msg = "no such loop device '"..id.."'"
|
||||
elseif msg:find("EBUSY") then msg = "'"..id.."' is still mounted - unmount first or omit -l"
|
||||
end
|
||||
print(name..": "..msg); syscall.exit(1); return
|
||||
end
|
||||
syscall.devctl(1, "sfgc", 10)
|
||||
print(name..": detached "..id)
|
||||
syscall.devctl(1, "sfgc", 1)
|
||||
return
|
||||
end
|
||||
|
||||
if #args < 1 then
|
||||
print(name..": missing mount point operand")
|
||||
print("try '"..name.." --help' for more information.")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
local mpt = args[1]
|
||||
if mpt:sub(1,1) ~= "/" then mpt = syscall.getcwd().."/"..mpt end
|
||||
|
||||
local loopIdToDetach = nil
|
||||
if not opts["no-detach"] then
|
||||
local lok, devs = pcall(syscall.lolist)
|
||||
if lok then
|
||||
loopIdToDetach = {}
|
||||
for id in pairs(devs) do
|
||||
loopIdToDetach[#loopIdToDetach + 1] = id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local ok, err = pcall(syscall.umount, mpt)
|
||||
if not ok then
|
||||
local msg = tostring(err)
|
||||
if msg:find("EPERM") then msg = "Permission denied"
|
||||
elseif msg:find("EINVAL") then msg = "'"..args[1].."' is not a mount point"
|
||||
elseif msg:find("EBUSY") then msg = "'"..args[1].."' is busy - close open files first"
|
||||
end
|
||||
print(name..": "..msg); syscall.exit(1); return
|
||||
end
|
||||
|
||||
syscall.devctl(1, "sfgc", 10)
|
||||
print(name..": unmounted "..mpt)
|
||||
syscall.devctl(1, "sfgc", 1)
|
||||
|
||||
if loopIdToDetach then
|
||||
for _, id in ipairs(loopIdToDetach) do
|
||||
local dok = pcall(syscall.lodetach, id)
|
||||
if dok then
|
||||
syscall.devctl(1, "sfgc", 14)
|
||||
print(name..": auto-detached "..id)
|
||||
syscall.devctl(1, "sfgc", 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
67
Src/Hyperion-bash/bin/useradd
Normal file
67
Src/Hyperion-bash/bin/useradd
Normal file
@@ -0,0 +1,67 @@
|
||||
--:Minify:--
|
||||
local args = {...}
|
||||
local i = 1
|
||||
local opt = { createHome = true }
|
||||
|
||||
while i <= #args do
|
||||
local a = args[i]
|
||||
if a == "-p" then i=i+1; opt.password = args[i]
|
||||
elseif a == "-g" then i=i+1; opt.gid = tonumber(args[i])
|
||||
elseif a == "-d" then i=i+1; opt.homedir = args[i]
|
||||
elseif a == "-s" then i=i+1; opt.shell = args[i]
|
||||
elseif a == "-M" then opt.createHome = false
|
||||
elseif a:sub(1,1) ~= "-" then opt.username = a
|
||||
else print("useradd: unknown option: " .. a); return end
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
if not opt.username then
|
||||
print("Usage: useradd [-p password] [-g gid] [-d homedir] [-s shell] [-M] <username>")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
local password = opt.password
|
||||
if not password then
|
||||
printInline("New password: ")
|
||||
password = ""
|
||||
while true do
|
||||
local ch = syscall.read(0)
|
||||
if not ch or ch == "" then
|
||||
elseif ch == "\n" then syscall.write(1,"\n"); break
|
||||
elseif ch == "\b" then
|
||||
if #password > 0 then password=password:sub(1,-2); syscall.write(1,"\b \b") end
|
||||
else password=password..ch; syscall.write(1,"*") end
|
||||
end
|
||||
printInline("Confirm password: ")
|
||||
local pw2 = ""
|
||||
while true do
|
||||
local ch = syscall.read(0)
|
||||
if not ch or ch == "" then
|
||||
elseif ch == "\n" then syscall.write(1,"\n"); break
|
||||
elseif ch == "\b" then
|
||||
if #pw2 > 0 then pw2=pw2:sub(1,-2); syscall.write(1,"\b \b") end
|
||||
else pw2=pw2..ch; syscall.write(1,"*") end
|
||||
end
|
||||
if password ~= pw2 then
|
||||
print("useradd: passwords do not match")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
end
|
||||
|
||||
local uid, err = syscall.newuser(
|
||||
opt.username, password, opt.gid, opt.homedir, opt.shell or "/bin/hysh"
|
||||
)
|
||||
if not uid then
|
||||
print("useradd: " .. tostring(err))
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
if opt.createHome then
|
||||
local home = opt.homedir or ("/home/" .. opt.username)
|
||||
local ok, e = pcall(syscall.mkdir, home)
|
||||
if not ok then
|
||||
print("useradd: warning: could not create home " .. home .. ": " .. tostring(e))
|
||||
end
|
||||
end
|
||||
|
||||
print("useradd: created user '" .. opt.username .. "' with uid=" .. tostring(uid))
|
||||
49
Src/Hyperion-bash/bin/userdel
Normal file
49
Src/Hyperion-bash/bin/userdel
Normal file
@@ -0,0 +1,49 @@
|
||||
--:Minify:--
|
||||
local args = {...}
|
||||
local removeHome = false
|
||||
local username = nil
|
||||
|
||||
for _, a in ipairs(args) do
|
||||
if a == "-r" then removeHome = true
|
||||
elseif a:sub(1,1) ~= "-" then username = a
|
||||
else print("userdel: unknown option: " .. a); syscall.exit(1); return end
|
||||
end
|
||||
|
||||
if not username then
|
||||
print("Usage: userdel [-r] <username>")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
local uid = syscall.getuid(username)
|
||||
if not uid then
|
||||
print("userdel: user '" .. username .. "' does not exist")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
local pwent = syscall.getpasswd(uid)
|
||||
|
||||
local ok, err = syscall.deleteuser(uid)
|
||||
if not ok then
|
||||
print("userdel: " .. tostring(err))
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
if removeHome and pwent and pwent.homedir then
|
||||
local fs = require("sys.fs")
|
||||
local ok2, err2 = pcall(function()
|
||||
local function rmdir(path)
|
||||
for _, f in ipairs(fs.list(path) or {}) do
|
||||
local full = path .. "/" .. f
|
||||
if fs.isDir(full) then rmdir(full)
|
||||
else syscall.remove(full) end
|
||||
end
|
||||
syscall.remove(path)
|
||||
end
|
||||
if fs.exists(pwent.homedir) then rmdir(pwent.homedir) end
|
||||
end)
|
||||
if not ok2 then
|
||||
print("userdel: warning: could not remove home: " .. tostring(err2))
|
||||
end
|
||||
end
|
||||
|
||||
print("userdel: deleted user '" .. username .. "'")
|
||||
49
Src/Hyperion-bash/bin/usermod
Normal file
49
Src/Hyperion-bash/bin/usermod
Normal file
@@ -0,0 +1,49 @@
|
||||
--:Minify:--
|
||||
local args = {...}
|
||||
local i = 1
|
||||
local opt = {}
|
||||
|
||||
while i <= #args do
|
||||
local a = args[i]
|
||||
if a == "-l" then i=i+1; opt.newname = args[i]
|
||||
elseif a == "-p" then i=i+1; opt.password = args[i]
|
||||
elseif a == "-g" then i=i+1; opt.gid = tonumber(args[i])
|
||||
elseif a == "-d" then i=i+1; opt.homedir = args[i]
|
||||
elseif a == "-s" then i=i+1; opt.shell = args[i]
|
||||
elseif a == "-L" then opt.lock = true
|
||||
elseif a == "-U" then opt.unlock = true
|
||||
elseif a:sub(1,1) ~= "-" then opt.username = a
|
||||
else print("usermod: unknown option: " .. a); syscall.exit(1); return end
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
if not opt.username then
|
||||
print("Usage: usermod [-l newname] [-p password] [-g gid] [-d homedir] [-s shell] [-L] [-U] <username>")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
if opt.lock and opt.unlock then
|
||||
print("usermod: -L and -U are mutually exclusive")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
local uid = syscall.getuid(opt.username)
|
||||
if not uid then
|
||||
print("usermod: user '" .. opt.username .. "' does not exist")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
local function apply(fn, ...)
|
||||
local ok, err = fn(...)
|
||||
if not ok then print("usermod: " .. tostring(err)); syscall.exit(1) end
|
||||
end
|
||||
|
||||
if opt.newname then apply(syscall.setusername, uid, opt.newname) end
|
||||
if opt.password then apply(syscall.setpassword, uid, opt.password) end
|
||||
if opt.gid then apply(syscall.setgid, uid, opt.gid) end
|
||||
if opt.homedir then apply(syscall.sethomedir, uid, opt.homedir) end
|
||||
if opt.shell then apply(syscall.setshell, uid, opt.shell) end
|
||||
if opt.lock then apply(syscall.lockuser, uid) end
|
||||
if opt.unlock then apply(syscall.unlockuser, uid) end
|
||||
|
||||
print("usermod: updated user '" .. opt.username .. "'")
|
||||
1
Src/Hyperion-bash/bin/whoami
Normal file
1
Src/Hyperion-bash/bin/whoami
Normal file
@@ -0,0 +1 @@
|
||||
print((syscall.getUsername() or "Unknown"))
|
||||
8
Src/Hyperion-bash/bin/yes
Normal file
8
Src/Hyperion-bash/bin/yes
Normal file
@@ -0,0 +1,8 @@
|
||||
local args = {...}
|
||||
while true do
|
||||
if #args == 0 then
|
||||
print("y")
|
||||
else
|
||||
print(table.concat(args, " "))
|
||||
end
|
||||
end
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"Name": "Hyperion core",
|
||||
"Version": "1.0.0",
|
||||
"Publishers": ["HyperionOS Dev Team"],
|
||||
"Description": "Core system files and librarys for HyperionOS",
|
||||
"Dependencies": [
|
||||
"https://git.astronand.dev/Hyperion/HyperionOS/archive/Hyperion-kernel-v1.0.0.pkg"
|
||||
],
|
||||
"Tar": "https://git.astronand.dev/Hyperion/HyperionOS/archive/Hyperion-core-v1.0.0.tar"
|
||||
}
|
||||
@@ -1,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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -1,57 +0,0 @@
|
||||
local sys = {}
|
||||
local fs = require("sys.fs")
|
||||
|
||||
function sys.spawn(func, name, envars, args)
|
||||
return coroutine.yield(0x10, func, name, envars, args)
|
||||
end
|
||||
|
||||
function sys.spawnFromFile(path, name, envars, args)
|
||||
local data = fs.readAllText(path)
|
||||
if not data then
|
||||
error("File not found: "..path,2)
|
||||
end
|
||||
local func, err = load(data, "@"..path)
|
||||
if not func then
|
||||
error("Error loading file "..path..": "..tostring(err),2)
|
||||
end
|
||||
return coroutine.yield(0x10, func, name, envars, args)
|
||||
end
|
||||
|
||||
function sys.spawnAndWait(func, name, envars, args)
|
||||
local task = coroutine.yield(0x10, func, name, envars, args)
|
||||
local oldsignal = sys.getSignalHandler(17)
|
||||
local exit = false
|
||||
sys.setSignalHandler(17, function()
|
||||
local tasks = sys.getChildrenTasks(task)
|
||||
if not tasks[task] then
|
||||
exit = true
|
||||
end
|
||||
end)
|
||||
while not exit do
|
||||
coroutine.yield()
|
||||
end
|
||||
sys.setSignalHandler(17, oldsignal)
|
||||
return task
|
||||
end
|
||||
|
||||
function sys.spawnFromFileAndWait(path, name, envars, args)
|
||||
local data = fs.readAllText(path)
|
||||
if not data then
|
||||
error("File not found: "..path,2)
|
||||
end
|
||||
local func, err = load(data, "@"..path)
|
||||
if not func then
|
||||
error("Error loading file "..path..": "..tostring(err),2)
|
||||
end
|
||||
return sys.spawnAndWait(func, name, envars, args)
|
||||
end
|
||||
|
||||
function sys.exit(code)
|
||||
return coroutine.yield(0x11, code)
|
||||
end
|
||||
|
||||
function sys.getTaskInfo(task)
|
||||
return coroutine.yield(0x12, task)
|
||||
end
|
||||
|
||||
return sys
|
||||
@@ -1,5 +0,0 @@
|
||||
local sys = {}
|
||||
sys.fs = require("sys.fs")
|
||||
sys.hpv = require("sys.hpv")
|
||||
sys.ipc = require("sys.ipc")
|
||||
return sys
|
||||
@@ -1,3 +0,0 @@
|
||||
local ipc = {}
|
||||
|
||||
return ipc
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
6
Src/Hyperion-core/lib/io
Normal file
@@ -0,0 +1,6 @@
|
||||
local io = {}
|
||||
local fs = require("sys.fs")
|
||||
|
||||
function io.open(path, mode)
|
||||
return fs.open(path, mode)
|
||||
end
|
||||
@@ -1,32 +1,32 @@
|
||||
--:Minify:--
|
||||
local fs={}
|
||||
|
||||
-- "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
|
||||
6
Src/Hyperion-core/lib/sys/hpv
Normal file
6
Src/Hyperion-core/lib/sys/hpv
Normal file
@@ -0,0 +1,6 @@
|
||||
local sys = {}
|
||||
local fs = require("sys.fs")
|
||||
|
||||
|
||||
|
||||
return sys
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user