formating better for spm packages
This commit is contained in:
162
Src/hysh/data/bin/chattr
Normal file
162
Src/hysh/data/bin/chattr
Normal file
@@ -0,0 +1,162 @@
|
||||
--:Minify:--
|
||||
-- supports +i/-i (immutable) stored in the file's cmeta/xattr field
|
||||
local name = syscall.getTask(syscall.getpid()).name
|
||||
local cloptions = { R = false, help = false }
|
||||
local args = {}
|
||||
local modeStr = nil
|
||||
|
||||
for _, v in ipairs({ ... }) do
|
||||
if v:sub(1, 2) == "--" then
|
||||
local opt = v:sub(3)
|
||||
if cloptions[opt] == nil then
|
||||
print(name .. ": unrecognized option '" .. v .. "'")
|
||||
print("try '" .. name .. " --help' for more information.")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
cloptions[opt] = true
|
||||
elseif v:sub(1, 1) == "-" and not v:match("^%-[%+%-]") then
|
||||
local isFlag = true
|
||||
for i = 2, #v do
|
||||
local c = v:sub(i,i)
|
||||
if cloptions[c] ~= nil then
|
||||
cloptions[c] = true
|
||||
else
|
||||
isFlag = false; break
|
||||
end
|
||||
end
|
||||
if not isFlag then
|
||||
modeStr = v
|
||||
end
|
||||
elseif v:sub(1,1) == "+" or (v:sub(1,1) == "-" and v:match("^%-[a-zA-Z]")) then
|
||||
modeStr = v
|
||||
else
|
||||
table.insert(args, v)
|
||||
end
|
||||
end
|
||||
|
||||
if cloptions.help then
|
||||
print("Usage: " .. name .. " [OPTION]... +-= ATTRS FILE...")
|
||||
print("Change file attributes on a filesystem.")
|
||||
print("")
|
||||
print("Attributes:")
|
||||
print(" i immutable: file cannot be modified, renamed, or deleted")
|
||||
print(" a append-only: file can only be appended to")
|
||||
print("")
|
||||
print("Operators: +attr add, -attr remove")
|
||||
print("Example: " .. name .. " +i /etc/passwd")
|
||||
print("")
|
||||
print("Options:")
|
||||
print(" -R operate on files and directories recursively")
|
||||
print(" --help display this help and exit")
|
||||
return
|
||||
end
|
||||
|
||||
if not modeStr or #args < 1 then
|
||||
print(name .. ": missing operand")
|
||||
print("try '" .. name .. " --help' for more information.")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
local op = modeStr:sub(1, 1)
|
||||
local attrs = modeStr:sub(2)
|
||||
|
||||
if op ~= "+" and op ~= "-" then
|
||||
print(name .. ": invalid operator '" .. op .. "' (use + or -)")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
local function getXattr(path)
|
||||
local stat = pcall(function() return syscall.stat(path) end) and syscall.stat(path)
|
||||
if stat then return stat.xattr or "" end
|
||||
return ""
|
||||
end
|
||||
|
||||
local IMMUTABLE_TAG = "|i"
|
||||
local APPENDONLY_TAG = "|a"
|
||||
|
||||
local function attrTag(c)
|
||||
if c == "i" then return IMMUTABLE_TAG
|
||||
elseif c == "a" then return APPENDONLY_TAG
|
||||
else return nil end
|
||||
end
|
||||
|
||||
local function chattrPath(path)
|
||||
local stat = syscall.stat(path)
|
||||
if not stat then
|
||||
print(name .. ": cannot stat '" .. path .. "': No such file or directory")
|
||||
return false
|
||||
end
|
||||
if stat.etype == 0x01 then
|
||||
return true
|
||||
end
|
||||
|
||||
if not syscall.setxattr then
|
||||
print(name .. ": kernel does not expose setxattr syscall; cannot modify attributes")
|
||||
syscall.exit(1); return false
|
||||
end
|
||||
|
||||
local xattr = stat.xattr or ""
|
||||
|
||||
for i = 1, #attrs do
|
||||
local c = attrs:sub(i, i)
|
||||
local tag = attrTag(c)
|
||||
if not tag then
|
||||
print(name .. ": unsupported attribute '" .. c .. "'")
|
||||
syscall.exit(1); return false
|
||||
end
|
||||
local hasTag = xattr:find(tag, 1, true)
|
||||
if op == "+" and not hasTag then
|
||||
xattr = xattr .. tag
|
||||
elseif op == "-" and hasTag then
|
||||
xattr = xattr:gsub(tag:gsub("|", "%%|"), "")
|
||||
end
|
||||
end
|
||||
|
||||
local ok, err = pcall(syscall.setxattr, path, xattr)
|
||||
if not ok then
|
||||
print(name .. ": cannot set attributes on '" .. path .. "': " .. tostring(err))
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local function chattrRecursive(path)
|
||||
if not chattrPath(path) then return end
|
||||
if syscall.type(path) == "directory" then
|
||||
local ok, list = pcall(syscall.listdir, path)
|
||||
if ok then
|
||||
for _, entry in ipairs(list) do
|
||||
local child = path
|
||||
if child:sub(-1) ~= "/" then child = child .. "/" end
|
||||
chattrRecursive(child .. entry)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local cwd = syscall.getcwd()
|
||||
local function absPath(p)
|
||||
if p:sub(1,1) ~= "/" then p = cwd .. "/" .. p end
|
||||
return p
|
||||
end
|
||||
|
||||
if not syscall.setxattr then
|
||||
print(name .. ": kernel does not expose setxattr; attributes cannot be persisted")
|
||||
print(name .. ": add sys[\"setxattr\"] = vfs.setxattr to 10_vfs.kmod to enable this")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
local exitCode = 0
|
||||
for i = 1, #args do
|
||||
local path = absPath(args[i])
|
||||
if not syscall.exists(path) then
|
||||
print(name .. ": cannot access '" .. args[i] .. "': No such file or directory")
|
||||
exitCode = 1
|
||||
elseif cloptions.R then
|
||||
chattrRecursive(path)
|
||||
else
|
||||
if not chattrPath(path) then exitCode = 1 end
|
||||
end
|
||||
end
|
||||
|
||||
syscall.exit(exitCode)
|
||||
117
Src/hysh/data/bin/chgrp
Normal file
117
Src/hysh/data/bin/chgrp
Normal file
@@ -0,0 +1,117 @@
|
||||
--:Minify:--
|
||||
-- chgrp: change group ownership
|
||||
local name = syscall.getTask(syscall.getpid()).name
|
||||
local cloptions = { R = false, help = false }
|
||||
local args = {}
|
||||
|
||||
for _, v in ipairs({ ... }) do
|
||||
if v:sub(1, 2) == "--" then
|
||||
local opt = v:sub(3)
|
||||
if cloptions[opt] == nil then
|
||||
print(name .. ": unrecognized option '" .. v .. "'")
|
||||
print("try '" .. name .. " --help' for more information.")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
cloptions[opt] = true
|
||||
elseif v:sub(1, 1) == "-" then
|
||||
for i = 2, #v do
|
||||
local opt = v:sub(i, i)
|
||||
if cloptions[opt] == nil then
|
||||
print(name .. ": invalid option '-" .. opt .. "'")
|
||||
print("try '" .. name .. " --help' for more information.")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
cloptions[opt] = true
|
||||
end
|
||||
else
|
||||
table.insert(args, v)
|
||||
end
|
||||
end
|
||||
|
||||
if cloptions.help then
|
||||
print("Usage: " .. name .. " [OPTION]... GROUP FILE...")
|
||||
print("Change the group of each FILE to GROUP.")
|
||||
print("GROUP may be a group name or numeric ID.")
|
||||
print("")
|
||||
print("Options:")
|
||||
print(" -R operate on files and directories recursively")
|
||||
print(" --help display this help and exit")
|
||||
return
|
||||
end
|
||||
|
||||
if #args < 2 then
|
||||
print(name .. ": missing operand")
|
||||
print("try '" .. name .. " --help' for more information.")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
local groupStr = args[1]
|
||||
|
||||
local function resolveGid(s)
|
||||
local n = tonumber(s)
|
||||
if n then return n end
|
||||
local uid = syscall.getuidbyname and syscall.getuidbyname(s)
|
||||
if uid then
|
||||
local pwent = syscall.getpasswd(uid)
|
||||
if pwent then return pwent.gid end
|
||||
end
|
||||
print(name .. ": invalid group: '" .. s .. "'")
|
||||
syscall.exit(1)
|
||||
end
|
||||
|
||||
local newGid = resolveGid(groupStr)
|
||||
|
||||
local function chgrpPath(path)
|
||||
local stat = syscall.stat(path)
|
||||
if not stat then
|
||||
print(name .. ": cannot stat '" .. path .. "': no such file or directory")
|
||||
return false
|
||||
end
|
||||
local ok, err = pcall(syscall.chown, path, stat.owner, newGid)
|
||||
if not ok then
|
||||
local msg = tostring(err)
|
||||
if msg:find("EPERM") or msg:find("EACCES") then
|
||||
msg = "operation not permitted (must be root)"
|
||||
elseif msg:find("ENOENT") then
|
||||
msg = "no such file or directory"
|
||||
end
|
||||
print(name .. ": cannot change group of '" .. path .. "': " .. msg)
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local function chgrpRecursive(path)
|
||||
if not chgrpPath(path) then return end
|
||||
if syscall.type(path) == "directory" then
|
||||
local ok, list = pcall(syscall.listdir, path)
|
||||
if ok then
|
||||
for _, entry in ipairs(list) do
|
||||
local child = path
|
||||
if child:sub(-1) ~= "/" then child = child .. "/" end
|
||||
chgrpRecursive(child .. entry)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local cwd = syscall.getcwd()
|
||||
local function absPath(p)
|
||||
if p:sub(1,1) ~= "/" then p = cwd .. "/" .. p end
|
||||
return p
|
||||
end
|
||||
|
||||
local exitCode = 0
|
||||
for i = 2, #args do
|
||||
local path = absPath(args[i])
|
||||
if not syscall.exists(path) then
|
||||
print(name .. ": cannot access '" .. args[i] .. "': No such file or directory")
|
||||
exitCode = 1
|
||||
elseif cloptions.R then
|
||||
chgrpRecursive(path)
|
||||
else
|
||||
if not chgrpPath(path) then exitCode = 1 end
|
||||
end
|
||||
end
|
||||
|
||||
syscall.exit(exitCode)
|
||||
268
Src/hysh/data/bin/chmod
Normal file
268
Src/hysh/data/bin/chmod
Normal file
@@ -0,0 +1,268 @@
|
||||
--:Minify:--
|
||||
local name = syscall.getTask(syscall.getpid()).name
|
||||
local cloptions = { R = false, help = false }
|
||||
local args = {}
|
||||
|
||||
for _, v in ipairs({ ... }) do
|
||||
if v:sub(1, 2) == "--" then
|
||||
local opt = v:sub(3)
|
||||
if cloptions[opt] == nil then
|
||||
print(name .. ": unrecognized option '" .. v .. "'")
|
||||
print("try '" .. name .. " --help' for more information.")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
cloptions[opt] = true
|
||||
elseif v:sub(1, 1) == "-" then
|
||||
for i = 2, #v do
|
||||
local opt = v:sub(i, i)
|
||||
if cloptions[opt] == nil then
|
||||
print(name .. ": invalid option '-" .. opt .. "'")
|
||||
print("try '" .. name .. " --help' for more information.")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
cloptions[opt] = true
|
||||
end
|
||||
else
|
||||
table.insert(args, v)
|
||||
end
|
||||
end
|
||||
|
||||
if cloptions.help then
|
||||
print("Usage: " .. name .. " [OPTION]... MODE FILE...")
|
||||
print("Change the file mode bits of each FILE to MODE.")
|
||||
print("")
|
||||
print("MODE may be octal (e.g. 755) or symbolic (e.g. u+x, go-w, a=r).")
|
||||
print("")
|
||||
print("Octal bit layout (Hyperion):")
|
||||
print(" owner: r=32 w=16 x=512 group: r=8 w=4 x=256")
|
||||
print(" world: r=2 w=1 x=128 suid=64")
|
||||
print(" Common: 644=rw-r--r-- 755=rwxr-xr-x 700=rwx------")
|
||||
print("")
|
||||
print("Symbolic: [ugoa][+-=][rwxs] (comma-separated list)")
|
||||
print("")
|
||||
print("Options:")
|
||||
print(" -R change files and directories recursively")
|
||||
print(" --help display this help and exit")
|
||||
return
|
||||
end
|
||||
|
||||
if #args < 2 then
|
||||
print(name .. ": missing operand")
|
||||
print("try '" .. name .. " --help' for more information.")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
local modeArg = args[1]
|
||||
|
||||
local P = {
|
||||
OWNER_R = 32, OWNER_W = 16, OWNER_X = 512,
|
||||
GROUP_R = 8, GROUP_W = 4, GROUP_X = 256,
|
||||
WORLD_R = 2, WORLD_W = 1, WORLD_X = 128,
|
||||
SUID = 64,
|
||||
}
|
||||
|
||||
local function bit_is_set(num, bit)
|
||||
return math.floor(num / (2 ^ bit)) % 2 == 1
|
||||
end
|
||||
|
||||
local function parseOctal(s)
|
||||
local n = tonumber(s, 8)
|
||||
if not n then return nil end
|
||||
|
||||
local result = 0
|
||||
if bit_is_set(n, 8) then result = result + P.OWNER_R end -- 0400
|
||||
if bit_is_set(n, 7) then result = result + P.OWNER_W end -- 0200
|
||||
if bit_is_set(n, 6) then result = result + P.OWNER_X end -- 0100
|
||||
|
||||
if bit_is_set(n, 5) then result = result + P.GROUP_R end -- 040
|
||||
if bit_is_set(n, 4) then result = result + P.GROUP_W end -- 020
|
||||
if bit_is_set(n, 3) then result = result + P.GROUP_X end -- 010
|
||||
|
||||
if bit_is_set(n, 2) then result = result + P.WORLD_R end -- 004
|
||||
if bit_is_set(n, 1) then result = result + P.WORLD_W end -- 002
|
||||
if bit_is_set(n, 0) then result = result + P.WORLD_X end -- 001
|
||||
|
||||
if bit_is_set(n, 11) then result = result + P.SUID end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
local function applySymbolic(modeStr, existingPerms)
|
||||
local perms = existingPerms
|
||||
|
||||
for clause in (modeStr .. ","):gmatch("([^,]+),") do
|
||||
local who_str, rest = clause:match("^([ugoa]*)([+%-=].+)$")
|
||||
if not who_str then
|
||||
print(name .. ": invalid mode: '" .. clause .. "'")
|
||||
syscall.exit(1); return nil
|
||||
end
|
||||
if who_str == "" or who_str == "a" then who_str = "ugo" end
|
||||
|
||||
local op = rest:sub(1, 1)
|
||||
local bits_str = rest:sub(2)
|
||||
|
||||
local mask = 0
|
||||
for i = 1, #bits_str do
|
||||
local c = bits_str:sub(i, i)
|
||||
for j = 1, #who_str do
|
||||
local w = who_str:sub(j, j)
|
||||
if c == "r" then
|
||||
if w == "u" then mask = mask + P.OWNER_R
|
||||
elseif w == "g" then mask = mask + P.GROUP_R
|
||||
elseif w == "o" then mask = mask + P.WORLD_R end
|
||||
elseif c == "w" then
|
||||
if w == "u" then mask = mask + P.OWNER_W
|
||||
elseif w == "g" then mask = mask + P.GROUP_W
|
||||
elseif w == "o" then mask = mask + P.WORLD_W end
|
||||
elseif c == "x" then
|
||||
if w == "u" then mask = mask + P.OWNER_X
|
||||
elseif w == "g" then mask = mask + P.GROUP_X
|
||||
elseif w == "o" then mask = mask + P.WORLD_X end
|
||||
elseif c == "s" then
|
||||
if w == "u" then mask = mask + P.SUID end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if op == "+" then
|
||||
perms = perms + (mask - (perms % (mask + 1) - perms % mask > 0 and 0 or 0))
|
||||
|
||||
perms = perms - (perms % 1)
|
||||
local function bor(a, b)
|
||||
local result, bit = 0, 1
|
||||
while a > 0 or b > 0 do
|
||||
if (a % 2 == 1) or (b % 2 == 1) then result = result + bit end
|
||||
a = math.floor(a / 2); b = math.floor(b / 2); bit = bit * 2
|
||||
end
|
||||
return result
|
||||
end
|
||||
perms = bor(perms, mask)
|
||||
elseif op == "-" then
|
||||
local function band(a, b)
|
||||
local result, bit = 0, 1
|
||||
while a > 0 and b > 0 do
|
||||
if (a % 2 == 1) and (b % 2 == 1) then result = result + bit end
|
||||
a = math.floor(a / 2); b = math.floor(b / 2); bit = bit * 2
|
||||
end
|
||||
return result
|
||||
end
|
||||
local function bxor(a, b)
|
||||
local result, bit = 0, 1
|
||||
while a > 0 or b > 0 do
|
||||
if (a % 2 == 1) ~= (b % 2 == 1) then result = result + bit end
|
||||
a = math.floor(a / 2); b = math.floor(b / 2); bit = bit * 2
|
||||
end
|
||||
return result
|
||||
end
|
||||
perms = bxor(perms, band(perms, mask))
|
||||
elseif op == "=" then
|
||||
local clearMask = 0
|
||||
for j = 1, #who_str do
|
||||
local w = who_str:sub(j, j)
|
||||
if w == "u" then clearMask = clearMask + P.OWNER_R + P.OWNER_W + P.OWNER_X + P.SUID
|
||||
elseif w == "g" then clearMask = clearMask + P.GROUP_R + P.GROUP_W + P.GROUP_X
|
||||
elseif w == "o" then clearMask = clearMask + P.WORLD_R + P.WORLD_W + P.WORLD_X end
|
||||
end
|
||||
local function bxor(a, b)
|
||||
local result, bit = 0, 1
|
||||
while a > 0 or b > 0 do
|
||||
if (a % 2 == 1) ~= (b % 2 == 1) then result = result + bit end
|
||||
a = math.floor(a / 2); b = math.floor(b / 2); bit = bit * 2
|
||||
end
|
||||
return result
|
||||
end
|
||||
local function band(a, b)
|
||||
local result, bit = 0, 1
|
||||
while a > 0 and b > 0 do
|
||||
if (a % 2 == 1) and (b % 2 == 1) then result = result + bit end
|
||||
a = math.floor(a / 2); b = math.floor(b / 2); bit = bit * 2
|
||||
end
|
||||
return result
|
||||
end
|
||||
local function bor(a, b)
|
||||
local result, bit = 0, 1
|
||||
while a > 0 or b > 0 do
|
||||
if (a % 2 == 1) or (b % 2 == 1) then result = result + bit end
|
||||
a = math.floor(a / 2); b = math.floor(b / 2); bit = bit * 2
|
||||
end
|
||||
return result
|
||||
end
|
||||
perms = bxor(perms, band(perms, clearMask))
|
||||
perms = bor(perms, mask)
|
||||
else
|
||||
print(name .. ": invalid operator in mode: '" .. clause .. "'")
|
||||
syscall.exit(1); return nil
|
||||
end
|
||||
end
|
||||
|
||||
return perms
|
||||
end
|
||||
|
||||
local function resolveMode(modeStr, existingPerms)
|
||||
if modeStr:match("^[0-7]+$") then
|
||||
local p = parseOctal(modeStr)
|
||||
if p then return p end
|
||||
end
|
||||
|
||||
return applySymbolic(modeStr, existingPerms)
|
||||
end
|
||||
|
||||
local function chmodPath(path)
|
||||
local stat, err = pcall(syscall.stat, path)
|
||||
local existingPerms = 0
|
||||
if stat then
|
||||
local s = syscall.stat(path)
|
||||
existingPerms = s and s.perms or 0
|
||||
end
|
||||
|
||||
local newPerms = resolveMode(modeArg, existingPerms)
|
||||
if newPerms == nil then return false end
|
||||
|
||||
local ok, cerr = pcall(syscall.chmod, path, newPerms)
|
||||
if not ok then
|
||||
local msg = tostring(cerr)
|
||||
if msg:find("EACCES") or msg:find("EPERM") then
|
||||
msg = "permission denied"
|
||||
elseif msg:find("ENOENT") then
|
||||
msg = "no such file or directory"
|
||||
end
|
||||
print(name .. ": cannot change permissions of '" .. path .. "': " .. msg)
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local function chmodRecursive(path)
|
||||
if not chmodPath(path) then return end
|
||||
if syscall.type(path) == "directory" then
|
||||
local ok, list = pcall(syscall.listdir, path)
|
||||
if ok then
|
||||
for _, entry in ipairs(list) do
|
||||
local child = path
|
||||
if child:sub(-1) ~= "/" then child = child .. "/" end
|
||||
chmodRecursive(child .. entry)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local cwd = syscall.getcwd()
|
||||
local function absPath(p)
|
||||
if p:sub(1,1) ~= "/" then p = cwd .. "/" .. p end
|
||||
return p
|
||||
end
|
||||
|
||||
local exitCode = 0
|
||||
for i = 2, #args do
|
||||
local path = absPath(args[i])
|
||||
if not syscall.exists(path) then
|
||||
print(name .. ": cannot access '" .. args[i] .. "': No such file or directory")
|
||||
exitCode = 1
|
||||
elseif cloptions.R then
|
||||
chmodRecursive(path)
|
||||
else
|
||||
if not chmodPath(path) then exitCode = 1 end
|
||||
end
|
||||
end
|
||||
|
||||
syscall.exit(exitCode)
|
||||
150
Src/hysh/data/bin/chown
Normal file
150
Src/hysh/data/bin/chown
Normal file
@@ -0,0 +1,150 @@
|
||||
--:Minify:--
|
||||
local name = syscall.getTask(syscall.getpid()).name
|
||||
local cloptions = { R = false, help = false }
|
||||
local args = {}
|
||||
|
||||
for _, v in ipairs({ ... }) do
|
||||
if v:sub(1, 2) == "--" then
|
||||
local opt = v:sub(3)
|
||||
if cloptions[opt] == nil then
|
||||
print(name .. ": unrecognized option '" .. v .. "'")
|
||||
print("try '" .. name .. " --help' for more information.")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
cloptions[opt] = true
|
||||
elseif v:sub(1, 1) == "-" then
|
||||
for i = 2, #v do
|
||||
local opt = v:sub(i, i)
|
||||
if cloptions[opt] == nil then
|
||||
print(name .. ": invalid option '-" .. opt .. "'")
|
||||
print("try '" .. name .. " --help' for more information.")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
cloptions[opt] = true
|
||||
end
|
||||
else
|
||||
table.insert(args, v)
|
||||
end
|
||||
end
|
||||
|
||||
if cloptions.help then
|
||||
print("Usage: " .. name .. " [OPTION]... OWNER[:GROUP] FILE...")
|
||||
print(" " .. name .. " [OPTION]... :GROUP FILE...")
|
||||
print("Change the owner and/or group of each FILE.")
|
||||
print("OWNER and GROUP may be names or numeric IDs.")
|
||||
print("")
|
||||
print("Options:")
|
||||
print(" -R operate on files and directories recursively")
|
||||
print(" --help display this help and exit")
|
||||
return
|
||||
end
|
||||
|
||||
if #args < 2 then
|
||||
print(name .. ": missing operand")
|
||||
print("try '" .. name .. " --help' for more information.")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
local spec = args[1]
|
||||
local ownerStr, groupStr
|
||||
|
||||
if spec:sub(1,1) == ":" then
|
||||
groupStr = spec:sub(2)
|
||||
else
|
||||
local colon = spec:find(":", 1, true)
|
||||
if colon then
|
||||
ownerStr = spec:sub(1, colon - 1)
|
||||
groupStr = spec:sub(colon + 1)
|
||||
if groupStr == "" then groupStr = nil end
|
||||
else
|
||||
ownerStr = spec
|
||||
end
|
||||
end
|
||||
|
||||
local function resolveUid(s)
|
||||
if not s or s == "" then return nil end
|
||||
local n = tonumber(s)
|
||||
if n then return n end
|
||||
local uid = syscall.getuidbyname and syscall.getuidbyname(s)
|
||||
if uid then return uid end
|
||||
print(name .. ": invalid user: '" .. s .. "'")
|
||||
syscall.exit(1)
|
||||
end
|
||||
|
||||
local function resolveGid(s)
|
||||
if not s or s == "" then return nil end
|
||||
local n = tonumber(s)
|
||||
if n then return n end
|
||||
local uid = syscall.getuidbyname and syscall.getuidbyname(s)
|
||||
if uid then
|
||||
local pwent = syscall.getpasswd(uid)
|
||||
if pwent then return pwent.gid end
|
||||
end
|
||||
print(name .. ": invalid group: '" .. s .. "'")
|
||||
syscall.exit(1)
|
||||
end
|
||||
|
||||
local newUid = resolveUid(ownerStr)
|
||||
local newGid = resolveGid(groupStr)
|
||||
|
||||
if newUid == nil and newGid == nil then
|
||||
print(name .. ": no owner or group specified")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
local function chownPath(path)
|
||||
local stat = syscall.stat(path)
|
||||
if not stat then
|
||||
print(name .. ": cannot stat '" .. path .. "': no such file or directory")
|
||||
return false
|
||||
end
|
||||
local uid = newUid ~= nil and newUid or stat.owner
|
||||
local gid = newGid ~= nil and newGid or stat.group
|
||||
local ok, err = pcall(syscall.chown, path, uid, gid)
|
||||
if not ok then
|
||||
local msg = tostring(err)
|
||||
if msg:find("EPERM") or msg:find("EACCES") then
|
||||
msg = "operation not permitted (must be root)"
|
||||
elseif msg:find("ENOENT") then
|
||||
msg = "no such file or directory"
|
||||
end
|
||||
print(name .. ": cannot change owner of '" .. path .. "': " .. msg)
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local function chownRecursive(path)
|
||||
if not chownPath(path) then return end
|
||||
if syscall.type(path) == "directory" then
|
||||
local ok, list = pcall(syscall.listdir, path)
|
||||
if ok then
|
||||
for _, entry in ipairs(list) do
|
||||
local child = path
|
||||
if child:sub(-1) ~= "/" then child = child .. "/" end
|
||||
chownRecursive(child .. entry)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local cwd = syscall.getcwd()
|
||||
local function absPath(p)
|
||||
if p:sub(1,1) ~= "/" then p = cwd .. "/" .. p end
|
||||
return p
|
||||
end
|
||||
|
||||
local exitCode = 0
|
||||
for i = 2, #args do
|
||||
local path = absPath(args[i])
|
||||
if not syscall.exists(path) then
|
||||
print(name .. ": cannot access '" .. args[i] .. "': No such file or directory")
|
||||
exitCode = 1
|
||||
elseif cloptions.R then
|
||||
chownRecursive(path)
|
||||
else
|
||||
if not chownPath(path) then exitCode = 1 end
|
||||
end
|
||||
end
|
||||
|
||||
syscall.exit(exitCode)
|
||||
83
Src/hysh/data/bin/chroot
Normal file
83
Src/hysh/data/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
|
||||
309
Src/hysh/data/bin/help
Normal file
309
Src/hysh/data/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", 0x000000)
|
||||
syscall.devctl(1, "sbgc", 0xDBDBDB)
|
||||
local pct = math.floor(math.min(100, (scroll + pageSize) / totalLines * 100))
|
||||
local status = string.format(" help -- line %d/%d (%d%%) [up/down: scroll q: quit] ",
|
||||
scroll + 1, totalLines, pct)
|
||||
if #status > screenW then status = status:sub(1, screenW) end
|
||||
syscall.devctl(1, "spos", 1, screenH)
|
||||
syscall.write(1, status .. string.rep(" ", screenW - #status))
|
||||
syscall.devctl(1, "sfgc", 0xFFFFFF)
|
||||
syscall.devctl(1, "sbgc", 0x000000)
|
||||
dirty = false
|
||||
end
|
||||
|
||||
if totalLines <= pageSize then
|
||||
syscall.devctl(1, "clear")
|
||||
syscall.devctl(1, "spos", 1, 1)
|
||||
for _, line in ipairs(lines) do
|
||||
syscall.devctl(1, "sfgc", line[2])
|
||||
syscall.write(1, line[1] .. "\n")
|
||||
end
|
||||
syscall.devctl(1, "sfgc", 0xFFFFFF)
|
||||
return
|
||||
end
|
||||
|
||||
render()
|
||||
while true do
|
||||
local ch = syscall.read(0)
|
||||
if not ch or ch == "" then
|
||||
elseif ch == "q" or ch == "Q" then
|
||||
break
|
||||
elseif ch == "\17" then
|
||||
if scroll > 0 then scroll = scroll - 1; dirty = true end
|
||||
elseif ch == "\18" then
|
||||
if scroll + pageSize < totalLines then scroll = scroll + 1; dirty = true end
|
||||
elseif ch == "\19" then
|
||||
scroll = math.max(0, scroll - pageSize); dirty = true
|
||||
elseif ch == "\20" then
|
||||
scroll = math.min(totalLines - pageSize, scroll + pageSize); dirty = true
|
||||
end
|
||||
if dirty then render() end
|
||||
end
|
||||
|
||||
syscall.devctl(1, "clear")
|
||||
syscall.devctl(1, "spos", 1, 1)
|
||||
syscall.devctl(1, "sfgc", 0xFFFFFF)
|
||||
syscall.devctl(1, "sbgc", 0x000000)
|
||||
100
Src/hysh/data/bin/hfetch
Normal file
100
Src/hysh/data/bin/hfetch
Normal file
@@ -0,0 +1,100 @@
|
||||
--:Minify:--
|
||||
-- Color indices (sfgc):
|
||||
-- 1=white, 2=red, 3=green, 4=blue, 5=cyan, 6=magenta, 7=yellow
|
||||
-- 8=orange, 9=lime, 10=lightcyan, 11=brown, 12=darkgrey, 13=lightgrey, 14=purple, 15=chartreuse, 16=black
|
||||
|
||||
local C_LOGO = 0x00FFFF -- cyan
|
||||
local C_WHITE = 0xFFFFFF -- white
|
||||
local C_LABEL = 0xDBDBDB -- light grey (key names)
|
||||
local C_SEP = 0x6D6D6D -- dark grey (---- separator)
|
||||
local C_USER = 0x00FF00 -- green (user@host)
|
||||
|
||||
local function c(col) syscall.devctl(1, "sfgc", col) end
|
||||
|
||||
local username = syscall.getUsername() or "Unknown"
|
||||
local hostname = syscall.getHostname() or "Unknown"
|
||||
local userhost = username .. "@" .. hostname
|
||||
|
||||
local function formatUptime(ms)
|
||||
local s = math.floor(ms / 1000)
|
||||
local m = math.floor(s / 60)
|
||||
local h = math.floor(m / 60)
|
||||
local d = math.floor(h / 24)
|
||||
s = s % 60; m = m % 60; h = h % 24
|
||||
local parts = {}
|
||||
if d > 0 then parts[#parts+1] = d .. "d" end
|
||||
if h > 0 then parts[#parts+1] = h .. "h" end
|
||||
if m > 0 then parts[#parts+1] = m .. "m" end
|
||||
parts[#parts+1] = s .. "s"
|
||||
return table.concat(parts, " ")
|
||||
end
|
||||
|
||||
local host_str = syscall.getHost() or "Unknown"
|
||||
local cc_ver = host_str:match("ComputerCraft ([%d%.]+)") or host_str
|
||||
|
||||
local info = {
|
||||
{nil, userhost},
|
||||
{nil, string.rep("-", #userhost)},
|
||||
{"OS", syscall.version() or "Unknown"},
|
||||
{"Host", cc_ver},
|
||||
{"Arch", syscall.arch() or "Unknown"},
|
||||
{"Uptime", formatUptime(syscall.getUptime() or 0)},
|
||||
{"Tasks", tostring(#(syscall.getTasks() or {}))},
|
||||
{"Shell", syscall.getEnviron("SHELL") or "Unknown"},
|
||||
{"Terminal", "tty1"},
|
||||
{"UID", tostring(syscall.getuid())},
|
||||
{"Packages", "n/a (spm)"},
|
||||
}
|
||||
|
||||
local logo = {
|
||||
".. *. .. ",
|
||||
" *= +@* +* ",
|
||||
" .@#. -@@@= :#@. ",
|
||||
" =@@+ *@@@# +@@= ",
|
||||
" %@@%: *@@@# -%@@% ",
|
||||
" :@@@@+ *@@@# .*@@@@: ",
|
||||
" :*@@@%- *@@@# -@@@@*: ",
|
||||
" =%@@#. *@@@# .#@@%= ",
|
||||
" :=. :*@@= *@@@# =@@+: .=: ",
|
||||
" %@#=..*# +@@@# #*..=#@# ",
|
||||
" .@@@@+=# .%@%: #=+@@@@. ",
|
||||
" .....=# -@= *+...:. ",
|
||||
" -*%*-@= - =@-*%*- ",
|
||||
" -@*. -@%. :%@- :*@- ",
|
||||
" .#@#@* ",
|
||||
" -#- ",
|
||||
" ",
|
||||
}
|
||||
|
||||
local lines = math.max(#logo, #info)
|
||||
for i = 1, lines do
|
||||
local logo_str = logo[i] or string.rep(" ", 36)
|
||||
|
||||
c(C_LOGO)
|
||||
printInline(logo_str)
|
||||
|
||||
c(C_LABEL)
|
||||
printInline("| ")
|
||||
|
||||
local row = info[i]
|
||||
if row then
|
||||
if row[1] == nil and i == 1 then
|
||||
-- user@host line
|
||||
c(C_USER)
|
||||
printInline(row[2])
|
||||
elseif row[1] == nil and i == 2 then
|
||||
-- separator line
|
||||
c(C_SEP)
|
||||
printInline(row[2])
|
||||
elseif row[1] then
|
||||
-- label: value
|
||||
c(C_LABEL)
|
||||
printInline(row[1] .. ": ")
|
||||
c(C_WHITE)
|
||||
printInline(row[2])
|
||||
end
|
||||
end
|
||||
|
||||
c(C_WHITE)
|
||||
print("")
|
||||
end
|
||||
1252
Src/hysh/data/bin/hysh
Normal file
1252
Src/hysh/data/bin/hysh
Normal file
File diff suppressed because it is too large
Load Diff
19
Src/hysh/data/bin/id
Normal file
19
Src/hysh/data/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))
|
||||
3
Src/hysh/data/bin/ll
Normal file
3
Src/hysh/data/bin/ll
Normal file
@@ -0,0 +1,3 @@
|
||||
local args={...}
|
||||
table.insert(args, "-lah")
|
||||
syscall.exec("/bin/ls", args)
|
||||
96
Src/hysh/data/bin/ln
Normal file
96
Src/hysh/data/bin/ln
Normal file
@@ -0,0 +1,96 @@
|
||||
--:Minify:--
|
||||
local name = syscall.getTask(syscall.getpid()).name
|
||||
local cloptions = { s = false, f = false, help = false }
|
||||
local args = {}
|
||||
|
||||
for _, v in ipairs({ ... }) do
|
||||
if v:sub(1, 2) == "--" then
|
||||
local opt = v:sub(3)
|
||||
if cloptions[opt] == nil then
|
||||
print(name .. ": unrecognized option '" .. v .. "'")
|
||||
print("try '" .. name .. " --help' for more information.")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
cloptions[opt] = true
|
||||
elseif v:sub(1, 1) == "-" then
|
||||
for i = 2, #v do
|
||||
local opt = v:sub(i, i)
|
||||
if cloptions[opt] == nil then
|
||||
print(name .. ": invalid option '-" .. opt .. "'")
|
||||
print("try '" .. name .. " --help' for more information.")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
cloptions[opt] = true
|
||||
end
|
||||
else
|
||||
table.insert(args, v)
|
||||
end
|
||||
end
|
||||
|
||||
if cloptions.help then
|
||||
print("Usage: " .. name .. " [OPTION]... TARGET LINK_NAME")
|
||||
print(" " .. name .. " [OPTION]... TARGET... DIRECTORY")
|
||||
print("Create links between files.")
|
||||
print("")
|
||||
print("Options:")
|
||||
print(" -s make symbolic links instead of hard links")
|
||||
print(" -f remove existing destination files")
|
||||
print(" --help display this help and exit")
|
||||
print("")
|
||||
print("With no -s, hard links are not supported (filesystem limitation).")
|
||||
print("Use -s for symbolic links.")
|
||||
return
|
||||
end
|
||||
|
||||
if #args < 2 then
|
||||
print(name .. ": missing operand")
|
||||
print("try '" .. name .. " --help' for more information.")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
if not cloptions.s then
|
||||
print(name .. ": hard links are not supported; use -s for symbolic links")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
local dest = args[#args]
|
||||
local destDir = syscall.type(dest) == "directory"
|
||||
|
||||
local function cwd()
|
||||
local d = syscall.getcwd()
|
||||
if d:sub(-1) ~= "/" then d = d .. "/" end
|
||||
return d
|
||||
end
|
||||
|
||||
local function absPath(p)
|
||||
if p:sub(1,1) ~= "/" then p = cwd() .. p end
|
||||
return p
|
||||
end
|
||||
|
||||
for i = 1, #args - 1 do
|
||||
local target = args[i]
|
||||
local linkPath
|
||||
|
||||
if destDir then
|
||||
local basename = target:match("[^/]+$") or target
|
||||
linkPath = absPath(dest)
|
||||
if linkPath:sub(-1) ~= "/" then linkPath = linkPath .. "/" end
|
||||
linkPath = linkPath .. basename
|
||||
else
|
||||
linkPath = absPath(dest)
|
||||
end
|
||||
|
||||
if cloptions.f and syscall.exists(linkPath) then
|
||||
local ok, err = pcall(syscall.remove, linkPath)
|
||||
if not ok then
|
||||
print(name .. ": cannot remove '" .. linkPath .. "': " .. tostring(err))
|
||||
syscall.exit(1); return
|
||||
end
|
||||
end
|
||||
|
||||
local ok, err = pcall(syscall.symlink, target, linkPath)
|
||||
if not ok then
|
||||
print(name .. ": failed to create symlink '" .. linkPath .. "' -> '" .. target .. "': " .. tostring(err))
|
||||
syscall.exit(1); return
|
||||
end
|
||||
end
|
||||
169
Src/hysh/data/bin/login
Normal file
169
Src/hysh/data/bin/login
Normal file
@@ -0,0 +1,169 @@
|
||||
--:Minify:--
|
||||
syscall.open("/dev/tty/1", "r") --stdin (fd 0)
|
||||
syscall.open("/dev/tty/1", "w") --stdout (fd 1)
|
||||
syscall.open("/dev/null", "w") --stderr (fd 2)
|
||||
|
||||
|
||||
local MAX_ATTEMPTS = 3
|
||||
|
||||
local function readLine(mask)
|
||||
local input = ""
|
||||
while true do
|
||||
local ch = syscall.read(0)
|
||||
if not ch or ch == "" then
|
||||
elseif ch == "\n" then
|
||||
syscall.write(1, "\n")
|
||||
return input
|
||||
elseif ch == "\b" then
|
||||
if #input > 0 then
|
||||
input = input:sub(1, -2)
|
||||
syscall.write(1, "\b \b")
|
||||
end
|
||||
else
|
||||
input = input .. ch
|
||||
syscall.write(1, mask or ch)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function firstBoot()
|
||||
local shadow = ""
|
||||
local _fd, _fderr = pcall(function()
|
||||
local fd = syscall.open("/etc/shadow", "r")
|
||||
shadow = syscall.read(fd, 65535) or ""
|
||||
syscall.close(fd)
|
||||
end)
|
||||
if shadow:match("%S") then return end
|
||||
|
||||
syscall.devctl(1, "clear")
|
||||
syscall.devctl(1, "spos", 1, 1)
|
||||
syscall.devctl(1, "sfgc", 0x00FF00)
|
||||
syscall.write(1, "HyperionOS First Boot Setup\n")
|
||||
syscall.devctl(1, "sfgc", 0xFFFFFF)
|
||||
syscall.write(1, "No root password is set. Please create one now.\n\n")
|
||||
|
||||
while true do
|
||||
syscall.write(1, "New root password: ")
|
||||
local pw1 = readLine("*")
|
||||
syscall.write(1, "Confirm password: ")
|
||||
local pw2 = readLine("*")
|
||||
|
||||
if pw1 ~= pw2 then
|
||||
syscall.devctl(1, "sfgc", 0xFF0000)
|
||||
syscall.write(1, "Passwords do not match. Try again.\n\n")
|
||||
syscall.devctl(1, "sfgc", 0xFFFFFF)
|
||||
elseif #pw1 < 6 then
|
||||
syscall.devctl(1, "sfgc", 0xFF0000)
|
||||
syscall.write(1, "Password too short (minimum 6 characters).\n\n")
|
||||
syscall.devctl(1, "sfgc", 0xFFFFFF)
|
||||
else
|
||||
local ok, err = syscall.setpassword(0, pw1)
|
||||
if ok then
|
||||
syscall.devctl(1, "sfgc", 0x00FF00)
|
||||
syscall.write(1, "Root password set.\n\n")
|
||||
syscall.devctl(1, "sfgc", 0xFFFFFF)
|
||||
sleep(0.5)
|
||||
break
|
||||
else
|
||||
syscall.devctl(1, "sfgc", 0xFF0000)
|
||||
syscall.write(1, "Error: " .. tostring(err) .. "\n")
|
||||
syscall.devctl(1, "sfgc", 0xFFFFFF)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function spawnShell(username, uid, shell, homedir)
|
||||
local existsOk, existsErr = pcall(syscall.exists, shell)
|
||||
if not existsOk or not existsErr then
|
||||
syscall.write(1, "login: shell not found: " .. shell .. "\n")
|
||||
sleep(2)
|
||||
return false
|
||||
end
|
||||
|
||||
local accessOk, accessErr = pcall(syscall.access, shell, "rx")
|
||||
|
||||
syscall.setEnviron("HOME", homedir)
|
||||
syscall.setEnviron("USER", username)
|
||||
syscall.setEnviron("SHELL", shell)
|
||||
syscall.setEnviron("PATH", "/bin/")
|
||||
|
||||
local setuidOk, setuidErr = pcall(syscall.setuid, uid)
|
||||
if not setuidOk then
|
||||
syscall.write(1, "login: setuid failed: " .. tostring(setuidErr) .. "\n")
|
||||
sleep(2)
|
||||
return false
|
||||
end
|
||||
|
||||
local chdirOk, chdirErr = pcall(syscall.chdir, homedir)
|
||||
if not chdirOk then
|
||||
pcall(syscall.chdir, "/")
|
||||
end
|
||||
|
||||
local ok, err = pcall(syscall.execspawn, shell, username .. ":shell")
|
||||
if not ok then
|
||||
syscall.write(1, "login: failed to launch shell: " .. tostring(err) .. "\n")
|
||||
sleep(2)
|
||||
return false
|
||||
end
|
||||
|
||||
syscall.exit(0)
|
||||
end
|
||||
|
||||
local function doLogin()
|
||||
syscall.devctl(1, "clear")
|
||||
syscall.devctl(1, "sfgc", 0xFFFFFF)
|
||||
syscall.devctl(1, "sbgc", 0x000000)
|
||||
syscall.devctl(1, "spos", 1, 1)
|
||||
|
||||
local hostname = syscall.getHostname() or "hyperion"
|
||||
syscall.write(1, "HyperionOS\n")
|
||||
syscall.write(1, hostname .. " login\n\n")
|
||||
|
||||
local attempts = 0
|
||||
while attempts < MAX_ATTEMPTS do
|
||||
syscall.devctl(1, "sfgc", 0xFFFFFF)
|
||||
syscall.write(1, "Username: ")
|
||||
local username = readLine(nil)
|
||||
|
||||
if username ~= "" then
|
||||
|
||||
syscall.write(1, "Password: ")
|
||||
local password = readLine("*")
|
||||
local uid = syscall.getuidbyname(username)
|
||||
|
||||
local ok, err = syscall.login(uid, password)
|
||||
if ok then
|
||||
local uid = syscall.getuid()
|
||||
local pwent = syscall.getpasswd(uid)
|
||||
|
||||
local shell = (pwent and pwent.shell) or "/bin/hysh"
|
||||
local homedir = (pwent and pwent.homedir) or "/"
|
||||
|
||||
syscall.devctl(1, "sfgc", 0x00FF00)
|
||||
syscall.write(1, "\nWelcome, " .. username .. "!\n")
|
||||
syscall.devctl(1, "sfgc", 0xFFFFFF)
|
||||
sleep(0.3)
|
||||
|
||||
spawnShell(username, uid, shell, homedir)
|
||||
return -- back to login prompt
|
||||
else
|
||||
attempts = attempts + 1
|
||||
sleep(1)
|
||||
syscall.devctl(1, "sfgc", 0xFF0000)
|
||||
syscall.write(1, "Login incorrect.\n\n")
|
||||
syscall.devctl(1, "sfgc", 0xFFFFFF)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
syscall.devctl(1, "sfgc", 0xFF0000)
|
||||
syscall.write(1, "Maximum login attempts exceeded.\n")
|
||||
syscall.devctl(1, "sfgc", 0xFFFFFF)
|
||||
sleep(5)
|
||||
end
|
||||
|
||||
firstBoot()
|
||||
while true do
|
||||
doLogin()
|
||||
end
|
||||
157
Src/hysh/data/bin/loimgcreate
Normal file
157
Src/hysh/data/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("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", 0x00FFFF)
|
||||
print(name..": extracted "..count.." file(s) to "..destPath)
|
||||
syscall.devctl(1, "sfgc", 0xFFFFFF)
|
||||
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", 0x00FFFF)
|
||||
print(name..": image written to "..imgPath)
|
||||
syscall.devctl(1, "sfgc", 0x6D00FF)
|
||||
print(string.format(" %d records, %d bytes", lineCount - 1, byteCount))
|
||||
syscall.devctl(1, "sfgc", 0xFFFFFF)
|
||||
129
Src/hysh/data/bin/losetup
Normal file
129
Src/hysh/data/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 0x00FFFF or 0x0000FF
|
||||
syscall.devctl(1, "sfgc", 0x00FF00)
|
||||
printInline(string.format("%-10s", id))
|
||||
syscall.devctl(1, "sfgc", colour)
|
||||
printInline(string.format("%-7s", "["..mode.."]"))
|
||||
syscall.devctl(1, "sfgc", 0xFFFFFF)
|
||||
print(" "..path)
|
||||
end
|
||||
if not any then
|
||||
syscall.devctl(1, "sfgc", 0x6D00FF)
|
||||
print(name..": no loop devices attached")
|
||||
syscall.devctl(1, "sfgc", 0xFFFFFF)
|
||||
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", 0x00FFFF)
|
||||
print(name..": detached "..id)
|
||||
syscall.devctl(1, "sfgc", 0xFFFFFF)
|
||||
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)
|
||||
203
Src/hysh/data/bin/ls
Normal file
203
Src/hysh/data/bin/ls
Normal file
@@ -0,0 +1,203 @@
|
||||
--:Minify:--
|
||||
local cloptions = {
|
||||
a = false,
|
||||
h = false,
|
||||
l = false,
|
||||
help = false,
|
||||
}
|
||||
local inpArgs = { ... }
|
||||
local args = {}
|
||||
local name = syscall.getTask(syscall.getpid()).name
|
||||
|
||||
for _, v in pairs(inpArgs) do
|
||||
if v:sub(1, 2) == "--" then
|
||||
local opt = v:sub(3)
|
||||
if cloptions[opt] == nil then
|
||||
print(name .. ": unrecognized option '" .. v .. "'.")
|
||||
print("try '" .. name .. " --help' for more information.")
|
||||
return
|
||||
end
|
||||
cloptions[opt] = true
|
||||
elseif v:sub(1, 1) == "-" then
|
||||
for i = 2, #v do
|
||||
local opt = v:sub(i, i)
|
||||
if cloptions[opt] == nil then
|
||||
print(name .. ": invalid option '-" .. opt .. "'.")
|
||||
print("try '" .. name .. " --help' for more information.")
|
||||
return
|
||||
end
|
||||
cloptions[opt] = true
|
||||
end
|
||||
else
|
||||
table.insert(args, v)
|
||||
end
|
||||
end
|
||||
|
||||
if cloptions.help then
|
||||
print("Usage: " .. name .. " [OPTION]... [DIR]")
|
||||
print("List all entries in the specified DIRectory, or cwd if not specified.")
|
||||
print("")
|
||||
print("Options:")
|
||||
print(" -a do not ignore entries starting with .")
|
||||
print(" -h with -l, print sizes in human readable format")
|
||||
print(" -l use a long listing format")
|
||||
print(" --help display this help and exit")
|
||||
return
|
||||
end
|
||||
|
||||
local fs = require("fs")
|
||||
local dir = args[1] or ""
|
||||
if dir:sub(1, 1) ~= "/" then
|
||||
dir = syscall.getcwd() .. "/" .. dir
|
||||
end
|
||||
if dir:sub(-1) ~= "/" then dir = dir .. "/" end
|
||||
|
||||
if not fs.isDir(dir) then
|
||||
print(name .. ": cannot access '" .. (args[1] or dir) .. "': no such directory")
|
||||
return
|
||||
end
|
||||
|
||||
local function permStr(perms, etype)
|
||||
local function b(n) return math.floor(perms / (2^n)) % 2 == 1 end
|
||||
local t
|
||||
if etype == 0x01 then t = "l"
|
||||
elseif etype == nil then t = "-"
|
||||
else t = "-" end
|
||||
|
||||
local ur = b(5) and "r" or "-"
|
||||
local uw = b(4) and "w" or "-"
|
||||
local ux = b(9) and (b(6) and "s" or "x") or (b(6) and "S" or "-")
|
||||
local gr = b(3) and "r" or "-"
|
||||
local gw = b(2) and "w" or "-"
|
||||
local gx = b(8) and "x" or "-"
|
||||
local wr = b(1) and "r" or "-"
|
||||
local ww = b(0) and "w" or "-"
|
||||
local wx = b(7) and "x" or "-"
|
||||
|
||||
return t .. ur .. uw .. ux .. gr .. gw .. gx .. wr .. ww .. wx
|
||||
end
|
||||
|
||||
local sizePrefixes = { "K", "M", "G", "T" }
|
||||
local function humanSize(size)
|
||||
local scale = 0
|
||||
while size >= 1024 and scale < #sizePrefixes do
|
||||
size = size / 1024
|
||||
scale = scale + 1
|
||||
end
|
||||
if scale == 0 then return tostring(size).."B" end
|
||||
if size < 10 then
|
||||
return string.format("%.1f%s", size, sizePrefixes[scale])
|
||||
end
|
||||
return math.floor(size) .. sizePrefixes[scale]
|
||||
end
|
||||
|
||||
local screenSizeStr = syscall.devctl(1, "size")
|
||||
local sizeX = tonumber(screenSizeStr:match("^(%d+)")) or 80
|
||||
|
||||
local list = fs.list(dir)
|
||||
list[#list+1] = "."
|
||||
list[#list+1] = ".."
|
||||
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 isSock = stat and stat.etype == 0x02
|
||||
|
||||
local typeChar
|
||||
if isSym then typeChar = "l"
|
||||
elseif isDir then typeChar = "d"
|
||||
elseif isSock then typeChar = "s"
|
||||
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", 0x00FFFF)
|
||||
printInline(v)
|
||||
syscall.devctl(1, "sfgc", 0xFFFFFF)
|
||||
local ok, target = pcall(syscall.readlink, fullPath)
|
||||
if ok then
|
||||
printInline(" -> ")
|
||||
local targetExists = pcall(syscall.stat, fullPath)
|
||||
syscall.devctl(1, "sfgc", targetExists and 0x00FFFF or 0xFF0000)
|
||||
printInline(target)
|
||||
syscall.devctl(1, "sfgc", 0xFFFFFF)
|
||||
end
|
||||
elseif isDir then
|
||||
syscall.devctl(1, "sfgc", 0x6D00FF)
|
||||
printInline(v)
|
||||
syscall.devctl(1, "sfgc", 0xFFFFFF)
|
||||
elseif isSock then
|
||||
syscall.devctl(1, "sfgc", 0xFF00FF)
|
||||
printInline(v)
|
||||
syscall.devctl(1, "sfgc", 0xFFFFFF)
|
||||
else
|
||||
local isExec = stat and stat.perms and (math.floor(stat.perms / (2^9)) % 2 == 1)
|
||||
syscall.devctl(1, "sfgc", isExec and 0x00FF00 or 0xFFFFFF)
|
||||
printInline(v)
|
||||
syscall.devctl(1, "sfgc", 0xFFFFFF)
|
||||
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", 0x00FFFF)
|
||||
elseif isDir then
|
||||
syscall.devctl(1, "sfgc", 0x6D00FF)
|
||||
elseif isSock then
|
||||
syscall.devctl(1, "sfgc", 0xFF00FF)
|
||||
else
|
||||
local isExec = stat and stat.perms and (math.floor(stat.perms / (2^9)) % 2 == 1)
|
||||
syscall.devctl(1, "sfgc", isExec and 0x00FF00 or 0xFFFFFF)
|
||||
end
|
||||
|
||||
printInline(v)
|
||||
syscall.devctl(1, "sfgc", 0xFFFFFF)
|
||||
printInline((" "):rep(colWidth - #v))
|
||||
|
||||
if i % numCols == 0 then print("") end
|
||||
end
|
||||
|
||||
if #list % numCols ~= 0 then print("") end
|
||||
19
Src/hysh/data/bin/lsusers
Normal file
19
Src/hysh/data/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",0xDBDBDB)
|
||||
print(string.format("%-6s %-6s %-16s %-20s %s", "UID", "GID", "Username", "Home", "Shell"))
|
||||
print(string.rep("-", 65))
|
||||
syscall.devctl(1,"sfgc",0xFFFFFF)
|
||||
|
||||
for _, u in ipairs(users) do
|
||||
local lock_marker = u.locked and " [locked]" or ""
|
||||
if u.locked then syscall.devctl(1,"sfgc",0xFF0000) end
|
||||
print(string.format("%-6d %-6d %-16s %-20s %s%s",
|
||||
u.uid, u.gid, u.username, u.homedir, u.shell, lock_marker))
|
||||
if u.locked then syscall.devctl(1,"sfgc",0xFFFFFF) end
|
||||
end
|
||||
152
Src/hysh/data/bin/mount
Normal file
152
Src/hysh/data/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", 0xFF00FF)
|
||||
print("(no loop devices attached)")
|
||||
syscall.devctl(1, "sfgc", 0xFFFFFF)
|
||||
return
|
||||
end
|
||||
|
||||
for id, info in pairs(loDevs) do
|
||||
local colour = info.mode == "image" and 0x00FFFF or 0x0000FF
|
||||
syscall.devctl(1, "sfgc", colour)
|
||||
printInline(info.mode.." "..id)
|
||||
syscall.devctl(1, "sfgc", 0xFFFFFF)
|
||||
print(" on "..info.path)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
if opts.o and opts.o:lower() == "loop" then
|
||||
if #args < 2 then
|
||||
print(name..": -o loop requires <source> and <mountpoint>")
|
||||
print("try '"..name.." --help' for more information.")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
local src = args[1]
|
||||
local dest = args[2]
|
||||
|
||||
if src:sub(1,1) ~= "/" then src = syscall.getcwd().."/"..src end
|
||||
if dest:sub(1,1) ~= "/" then dest = syscall.getcwd().."/"..dest end
|
||||
|
||||
local ok, loopId = pcall(syscall.losetup, src)
|
||||
if not ok then
|
||||
local msg = tostring(loopId)
|
||||
if msg:find("EPERM") then msg = "Permission denied"
|
||||
elseif msg:find("EINVAL") then msg = "'"..args[1].."': not a directory or .hfs image"
|
||||
elseif msg:find("EIO") then msg = "'"..args[1].."': I/O error reading image"
|
||||
end
|
||||
print(name..": losetup: "..msg); syscall.exit(1); return
|
||||
end
|
||||
|
||||
local ok2, merr = pcall(syscall.mount, dest, loopId)
|
||||
if not ok2 then
|
||||
pcall(syscall.lodetach, loopId)
|
||||
local msg = tostring(merr)
|
||||
if msg:find("EPERM") then msg = "Permission denied"
|
||||
elseif msg:find("EBUSY") then msg = "'"..dest.."' is already a mount point"
|
||||
elseif msg:find("ENODEV") then msg = "loop device not found (internal error)"
|
||||
end
|
||||
print(name..": mount: "..msg); syscall.exit(1); return
|
||||
end
|
||||
|
||||
syscall.devctl(1, "sfgc", 0x00FFFF)
|
||||
print(name..": "..loopId.." mounted at "..dest)
|
||||
syscall.devctl(1, "sfgc", 0xFFFFFF)
|
||||
return
|
||||
end
|
||||
|
||||
if #args == 2 then
|
||||
local loopId = args[1]
|
||||
local dest = args[2]
|
||||
if dest:sub(1,1) ~= "/" then dest = syscall.getcwd().."/"..dest end
|
||||
|
||||
local ok, err = pcall(syscall.mount, dest, loopId)
|
||||
if not ok then
|
||||
local msg = tostring(err)
|
||||
if msg:find("EPERM") then msg = "Permission denied"
|
||||
elseif msg:find("ENODEV") then msg = "'"..loopId.."': no such device - use losetup first"
|
||||
elseif msg:find("EBUSY") then msg = "'"..dest.."' is already a mount point"
|
||||
elseif msg:find("EINVAL") then msg = "invalid arguments"
|
||||
end
|
||||
print(name..": "..msg); syscall.exit(1); return
|
||||
end
|
||||
|
||||
syscall.devctl(1, "sfgc", 0x00FFFF)
|
||||
print(name..": "..loopId.." mounted at "..dest)
|
||||
syscall.devctl(1, "sfgc", 0xFFFFFF)
|
||||
return
|
||||
end
|
||||
|
||||
print(name..": wrong number of arguments")
|
||||
print("try '"..name.." --help' for more information.")
|
||||
syscall.exit(1)
|
||||
80
Src/hysh/data/bin/passwd
Normal file
80
Src/hysh/data/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()
|
||||
if not targetUid then
|
||||
print("passwd: user '" .. targetName .. "' does not exist")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
-- Only root can change another user's password
|
||||
if currentUid ~= 0 and targetUid ~= currentUid then
|
||||
print("passwd: permission denied")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
else
|
||||
targetUid = currentUid
|
||||
targetName = syscall.getUsername(currentUid) or tostring(currentUid)
|
||||
end
|
||||
|
||||
-- Non-root must verify their current password first
|
||||
if currentUid ~= 0 then
|
||||
printInline("Current password: ")
|
||||
local cur = ""
|
||||
while true do
|
||||
local ch = syscall.read(0)
|
||||
if not ch or ch == "" then
|
||||
elseif ch == "\n" then syscall.write(1,"\n"); break
|
||||
elseif ch == "\b" then
|
||||
if #cur > 0 then cur=cur:sub(1,-2); syscall.write(1,"\b \b") end
|
||||
else cur=cur..ch; syscall.write(1,"*") end
|
||||
end
|
||||
local ok, err = syscall.login(targetUid, cur)
|
||||
if not ok then
|
||||
sleep(1)
|
||||
print("passwd: authentication failure")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
end
|
||||
|
||||
printInline("New password: ")
|
||||
local pw1 = ""
|
||||
while true do
|
||||
local ch = syscall.read(0)
|
||||
if not ch or ch == "" then
|
||||
elseif ch == "\n" then syscall.write(1,"\n"); break
|
||||
elseif ch == "\b" then
|
||||
if #pw1 > 0 then pw1=pw1:sub(1,-2); syscall.write(1,"\b \b") end
|
||||
else pw1=pw1..ch; syscall.write(1,"*") end
|
||||
end
|
||||
|
||||
printInline("Confirm password: ")
|
||||
local pw2 = ""
|
||||
while true do
|
||||
local ch = syscall.read(0)
|
||||
if not ch or ch == "" then
|
||||
elseif ch == "\n" then syscall.write(1,"\n"); break
|
||||
elseif ch == "\b" then
|
||||
if #pw2 > 0 then pw2=pw2:sub(1,-2); syscall.write(1,"\b \b") end
|
||||
else pw2=pw2..ch; syscall.write(1,"*") end
|
||||
end
|
||||
|
||||
if pw1 ~= pw2 then
|
||||
print("passwd: passwords do not match")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
local ok, err = syscall.setpassword(targetUid, pw1)
|
||||
if not ok then
|
||||
print("passwd: " .. tostring(err))
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
print("passwd: password updated for '" .. targetName .. "'")
|
||||
5
Src/hysh/data/bin/ps
Normal file
5
Src/hysh/data/bin/ps
Normal file
@@ -0,0 +1,5 @@
|
||||
--:Minify:--
|
||||
for i,v in ipairs(syscall.getTasks()) do
|
||||
local task = syscall.getTask(v)
|
||||
print(task.pid,task.username,task.name,task.status)
|
||||
end
|
||||
82
Src/hysh/data/bin/readlink
Normal file
82
Src/hysh/data/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
|
||||
425
Src/hysh/data/bin/sed
Normal file
425
Src/hysh/data/bin/sed
Normal file
@@ -0,0 +1,425 @@
|
||||
--: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
|
||||
elseif c == "#" then
|
||||
while pos <= len and src:sub(pos,pos) ~= "\n" do pos = pos + 1 end
|
||||
else
|
||||
|
||||
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
|
||||
end
|
||||
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
|
||||
65
Src/hysh/data/bin/su
Normal file
65
Src/hysh/data/bin/su
Normal file
@@ -0,0 +1,65 @@
|
||||
--:Minify:--
|
||||
local targetUser = ({ ... })[1]
|
||||
local currentUid = syscall.getuid()
|
||||
if syscall.geteuid()~=0 then
|
||||
syscall.exec("/bin/su", {...})
|
||||
end
|
||||
local targetUid
|
||||
if targetUser then
|
||||
targetUid = syscall.getuidbyname(targetUser)
|
||||
else
|
||||
targetUid = 0
|
||||
end
|
||||
|
||||
if not targetUid then
|
||||
print("su: user '" .. targetUser .. "' does not exist")
|
||||
syscall.exit(1)
|
||||
return
|
||||
end
|
||||
|
||||
if currentUid ~= 0 then
|
||||
printInline("Password: ")
|
||||
local pw = ""
|
||||
while true do
|
||||
local ch = syscall.read(0)
|
||||
if not ch or ch == "" then
|
||||
elseif ch == "\n" then
|
||||
syscall.write(1, "\n")
|
||||
break
|
||||
elseif ch == "\b" then
|
||||
if #pw > 0 then pw = pw:sub(1, -2); syscall.write(1, "\b \b") end
|
||||
else
|
||||
pw = pw .. ch; syscall.write(1, "*")
|
||||
end
|
||||
end
|
||||
|
||||
local ok, err = syscall.login(targetUid, pw)
|
||||
if not ok then
|
||||
sleep(1)
|
||||
print("su: Authentication failure")
|
||||
syscall.exit(1)
|
||||
return
|
||||
end
|
||||
else
|
||||
syscall.setuid(targetUid)
|
||||
end
|
||||
|
||||
local pwent = syscall.getpasswd(targetUid)
|
||||
local shell = (pwent and pwent.shell) or "/bin/hysh"
|
||||
local homedir = (pwent and pwent.homedir) or "/"
|
||||
local username= (pwent and pwent.username)or "Unknown"
|
||||
|
||||
local ok_cd, err_cd = pcall(syscall.chdir, homedir)
|
||||
if not ok_cd then
|
||||
homedir = "/"
|
||||
syscall.chdir(homedir)
|
||||
end
|
||||
syscall.setEnviron("HOME", homedir)
|
||||
syscall.setEnviron("USER", username)
|
||||
syscall.setEnviron("SHELL", shell)
|
||||
|
||||
local ok, err = pcall(syscall.exec, shell)
|
||||
if not ok then
|
||||
print("su: cannot exec shell '" .. shell .. "': " .. tostring(err))
|
||||
syscall.exit(1)
|
||||
end
|
||||
110
Src/hysh/data/bin/sudo
Normal file
110
Src/hysh/data/bin/sudo
Normal file
@@ -0,0 +1,110 @@
|
||||
--:Minify:--
|
||||
local fs = require("fs")
|
||||
|
||||
local cmdArgs = {...}
|
||||
local targetUser = "root"
|
||||
local i = 1
|
||||
|
||||
if cmdArgs[i] == "-u" then
|
||||
i = i + 1
|
||||
local uarg = cmdArgs[i] or "root"
|
||||
local numUid = tonumber(uarg)
|
||||
if numUid then
|
||||
local pwent = syscall.getpasswd(numUid)
|
||||
targetUser = (pwent and pwent.username) or uarg
|
||||
else
|
||||
targetUser = uarg
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
local cmd = cmdArgs[i]
|
||||
if not cmd or cmd == "" then
|
||||
print("usage: sudo [-u user] <command> [args...]")
|
||||
syscall.exit(1)
|
||||
return
|
||||
end
|
||||
|
||||
local restArgs = {}
|
||||
for j = i + 1, #cmdArgs do restArgs[#restArgs + 1] = cmdArgs[j] end
|
||||
|
||||
local currentUid = syscall.getuid()
|
||||
local currentUser = syscall.getUsername(currentUid) or tostring(currentUid)
|
||||
|
||||
local targetUid = syscall.getuidbyname(targetUser)
|
||||
if not targetUid then
|
||||
print("sudo: user '" .. targetUser .. "' does not exist")
|
||||
syscall.exit(1)
|
||||
return
|
||||
end
|
||||
|
||||
if currentUid ~= 0 then
|
||||
printInline("[sudo] password for root: ")
|
||||
local pw = ""
|
||||
while true do
|
||||
local ch = syscall.read(0)
|
||||
if not ch or ch == "" then
|
||||
elseif ch == "\n" then
|
||||
syscall.write(1, "\n")
|
||||
break
|
||||
elseif ch == "\b" then
|
||||
if #pw > 0 then pw = pw:sub(1, -2); syscall.write(1, "\b \b") end
|
||||
else
|
||||
pw = pw .. ch
|
||||
syscall.write(1, "*")
|
||||
end
|
||||
end
|
||||
|
||||
local ok, err = syscall.login(0, pw)
|
||||
if not ok then
|
||||
sleep(1)
|
||||
print("sudo: Authentication failure")
|
||||
syscall.exit(1)
|
||||
return
|
||||
end
|
||||
|
||||
if targetUid ~= currentUid then
|
||||
syscall.setuid(targetUid)
|
||||
end
|
||||
else
|
||||
if targetUid ~= currentUid then
|
||||
syscall.setuid(targetUid)
|
||||
end
|
||||
end
|
||||
|
||||
local cmdPath = ""
|
||||
if cmd:find("/") then
|
||||
if fs.exists(cmd) then cmdPath = cmd end
|
||||
else
|
||||
local paths = string.split(syscall.getEnviron("PATH") or "/bin/", ":")
|
||||
for _, p in ipairs(paths) do
|
||||
local full = p .. cmd
|
||||
if fs.exists(full) then cmdPath = full; break end
|
||||
end
|
||||
end
|
||||
|
||||
if cmdPath == "" then
|
||||
print("sudo: command not found: " .. cmd)
|
||||
syscall.exit(1)
|
||||
return
|
||||
end
|
||||
|
||||
local text = fs.readAllText(cmdPath)
|
||||
local program, loadErr = load(text, "@" .. cmdPath)
|
||||
if not program then
|
||||
print("sudo: cannot load " .. cmd .. ": " .. tostring(loadErr))
|
||||
syscall.exit(1)
|
||||
return
|
||||
end
|
||||
|
||||
local pwent = syscall.getpasswd(targetUid)
|
||||
if pwent and pwent.homedir then
|
||||
syscall.setEnviron("HOME", pwent.homedir)
|
||||
end
|
||||
syscall.setEnviron("USER", targetUser)
|
||||
|
||||
local ok, err = xpcall(program, debug.traceback, table.unpack(restArgs))
|
||||
if not ok then
|
||||
print("sudo: " .. cmd .. ": " .. tostring(err))
|
||||
syscall.exit(1)
|
||||
end
|
||||
10
Src/hysh/data/bin/sysdump
Normal file
10
Src/hysh/data/bin/sysdump
Normal file
@@ -0,0 +1,10 @@
|
||||
--:Minify:--
|
||||
local path=...
|
||||
path=path or "/dev/tty/1"
|
||||
local syscalls=syscall.sysdump()
|
||||
local fd=syscall.open(path,"w")
|
||||
for i=1, #syscalls do
|
||||
syscall.write(fd,syscalls[i].."\n")
|
||||
end
|
||||
syscall.write(fd,"Total # of syscalls: "..tostring(#syscalls))
|
||||
syscall.close(fd)
|
||||
111
Src/hysh/data/bin/umount
Normal file
111
Src/hysh/data/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", 0x00FFFF)
|
||||
print(name..": detached "..id)
|
||||
syscall.devctl(1, "sfgc", 0xFFFFFF)
|
||||
return
|
||||
end
|
||||
|
||||
if #args < 1 then
|
||||
print(name..": missing mount point operand")
|
||||
print("try '"..name.." --help' for more information.")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
local mpt = args[1]
|
||||
if mpt:sub(1,1) ~= "/" then mpt = syscall.getcwd().."/"..mpt end
|
||||
|
||||
local loopIdToDetach = nil
|
||||
if not opts["no-detach"] then
|
||||
local lok, devs = pcall(syscall.lolist)
|
||||
if lok then
|
||||
loopIdToDetach = {}
|
||||
for id in pairs(devs) do
|
||||
loopIdToDetach[#loopIdToDetach + 1] = id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local ok, err = pcall(syscall.umount, mpt)
|
||||
if not ok then
|
||||
local msg = tostring(err)
|
||||
if msg:find("EPERM") then msg = "Permission denied"
|
||||
elseif msg:find("EINVAL") then msg = "'"..args[1].."' is not a mount point"
|
||||
elseif msg:find("EBUSY") then msg = "'"..args[1].."' is busy - close open files first"
|
||||
end
|
||||
print(name..": "..msg); syscall.exit(1); return
|
||||
end
|
||||
|
||||
syscall.devctl(1, "sfgc", 0x00FFFF)
|
||||
print(name..": unmounted "..mpt)
|
||||
syscall.devctl(1, "sfgc", 0xFFFFFF)
|
||||
|
||||
if loopIdToDetach then
|
||||
for _, id in ipairs(loopIdToDetach) do
|
||||
local dok = pcall(syscall.lodetach, id)
|
||||
if dok then
|
||||
syscall.devctl(1, "sfgc", 0xFF00FF)
|
||||
print(name..": auto-detached "..id)
|
||||
syscall.devctl(1, "sfgc", 0xFFFFFF)
|
||||
end
|
||||
end
|
||||
end
|
||||
67
Src/hysh/data/bin/useradd
Normal file
67
Src/hysh/data/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/hysh/data/bin/userdel
Normal file
49
Src/hysh/data/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("fs")
|
||||
local ok2, err2 = pcall(function()
|
||||
local function rmdir(path)
|
||||
for _, f in ipairs(fs.list(path) or {}) do
|
||||
local full = path .. "/" .. f
|
||||
if fs.isDir(full) then rmdir(full)
|
||||
else syscall.remove(full) end
|
||||
end
|
||||
syscall.remove(path)
|
||||
end
|
||||
if fs.exists(pwent.homedir) then rmdir(pwent.homedir) end
|
||||
end)
|
||||
if not ok2 then
|
||||
print("userdel: warning: could not remove home: " .. tostring(err2))
|
||||
end
|
||||
end
|
||||
|
||||
print("userdel: deleted user '" .. username .. "'")
|
||||
49
Src/hysh/data/bin/usermod
Normal file
49
Src/hysh/data/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 .. "'")
|
||||
9
Src/hysh/data/bin/yes
Normal file
9
Src/hysh/data/bin/yes
Normal file
@@ -0,0 +1,9 @@
|
||||
--:Minify:--
|
||||
local args = {...}
|
||||
while true do
|
||||
if #args == 0 then
|
||||
print("y")
|
||||
else
|
||||
print(table.concat(args, " "))
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user