formating better for spm packages

This commit is contained in:
2026-03-26 08:12:37 -04:00
parent 22b5021e9d
commit ab0a10105c
193 changed files with 4228 additions and 464 deletions

162
Src/hysh/data/bin/chattr Normal file
View File

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

117
Src/hysh/data/bin/chgrp Normal file
View File

@@ -0,0 +1,117 @@
--:Minify:--
-- chgrp: change group ownership
local name = syscall.getTask(syscall.getpid()).name
local cloptions = { R = false, help = false }
local args = {}
for _, v in ipairs({ ... }) do
if v:sub(1, 2) == "--" then
local opt = v:sub(3)
if cloptions[opt] == nil then
print(name .. ": unrecognized option '" .. v .. "'")
print("try '" .. name .. " --help' for more information.")
syscall.exit(1); return
end
cloptions[opt] = true
elseif v:sub(1, 1) == "-" then
for i = 2, #v do
local opt = v:sub(i, i)
if cloptions[opt] == nil then
print(name .. ": invalid option '-" .. opt .. "'")
print("try '" .. name .. " --help' for more information.")
syscall.exit(1); return
end
cloptions[opt] = true
end
else
table.insert(args, v)
end
end
if cloptions.help then
print("Usage: " .. name .. " [OPTION]... GROUP FILE...")
print("Change the group of each FILE to GROUP.")
print("GROUP may be a group name or numeric ID.")
print("")
print("Options:")
print(" -R operate on files and directories recursively")
print(" --help display this help and exit")
return
end
if #args < 2 then
print(name .. ": missing operand")
print("try '" .. name .. " --help' for more information.")
syscall.exit(1); return
end
local groupStr = args[1]
local function resolveGid(s)
local n = tonumber(s)
if n then return n end
local uid = syscall.getuidbyname and syscall.getuidbyname(s)
if uid then
local pwent = syscall.getpasswd(uid)
if pwent then return pwent.gid end
end
print(name .. ": invalid group: '" .. s .. "'")
syscall.exit(1)
end
local newGid = resolveGid(groupStr)
local function chgrpPath(path)
local stat = syscall.stat(path)
if not stat then
print(name .. ": cannot stat '" .. path .. "': no such file or directory")
return false
end
local ok, err = pcall(syscall.chown, path, stat.owner, newGid)
if not ok then
local msg = tostring(err)
if msg:find("EPERM") or msg:find("EACCES") then
msg = "operation not permitted (must be root)"
elseif msg:find("ENOENT") then
msg = "no such file or directory"
end
print(name .. ": cannot change group of '" .. path .. "': " .. msg)
return false
end
return true
end
local function chgrpRecursive(path)
if not chgrpPath(path) then return end
if syscall.type(path) == "directory" then
local ok, list = pcall(syscall.listdir, path)
if ok then
for _, entry in ipairs(list) do
local child = path
if child:sub(-1) ~= "/" then child = child .. "/" end
chgrpRecursive(child .. entry)
end
end
end
end
local cwd = syscall.getcwd()
local function absPath(p)
if p:sub(1,1) ~= "/" then p = cwd .. "/" .. p end
return p
end
local exitCode = 0
for i = 2, #args do
local path = absPath(args[i])
if not syscall.exists(path) then
print(name .. ": cannot access '" .. args[i] .. "': No such file or directory")
exitCode = 1
elseif cloptions.R then
chgrpRecursive(path)
else
if not chgrpPath(path) then exitCode = 1 end
end
end
syscall.exit(exitCode)

268
Src/hysh/data/bin/chmod Normal file
View File

@@ -0,0 +1,268 @@
--:Minify:--
local name = syscall.getTask(syscall.getpid()).name
local cloptions = { R = false, help = false }
local args = {}
for _, v in ipairs({ ... }) do
if v:sub(1, 2) == "--" then
local opt = v:sub(3)
if cloptions[opt] == nil then
print(name .. ": unrecognized option '" .. v .. "'")
print("try '" .. name .. " --help' for more information.")
syscall.exit(1); return
end
cloptions[opt] = true
elseif v:sub(1, 1) == "-" then
for i = 2, #v do
local opt = v:sub(i, i)
if cloptions[opt] == nil then
print(name .. ": invalid option '-" .. opt .. "'")
print("try '" .. name .. " --help' for more information.")
syscall.exit(1); return
end
cloptions[opt] = true
end
else
table.insert(args, v)
end
end
if cloptions.help then
print("Usage: " .. name .. " [OPTION]... MODE FILE...")
print("Change the file mode bits of each FILE to MODE.")
print("")
print("MODE may be octal (e.g. 755) or symbolic (e.g. u+x, go-w, a=r).")
print("")
print("Octal bit layout (Hyperion):")
print(" owner: r=32 w=16 x=512 group: r=8 w=4 x=256")
print(" world: r=2 w=1 x=128 suid=64")
print(" Common: 644=rw-r--r-- 755=rwxr-xr-x 700=rwx------")
print("")
print("Symbolic: [ugoa][+-=][rwxs] (comma-separated list)")
print("")
print("Options:")
print(" -R change files and directories recursively")
print(" --help display this help and exit")
return
end
if #args < 2 then
print(name .. ": missing operand")
print("try '" .. name .. " --help' for more information.")
syscall.exit(1); return
end
local modeArg = args[1]
local P = {
OWNER_R = 32, OWNER_W = 16, OWNER_X = 512,
GROUP_R = 8, GROUP_W = 4, GROUP_X = 256,
WORLD_R = 2, WORLD_W = 1, WORLD_X = 128,
SUID = 64,
}
local function bit_is_set(num, bit)
return math.floor(num / (2 ^ bit)) % 2 == 1
end
local function parseOctal(s)
local n = tonumber(s, 8)
if not n then return nil end
local result = 0
if bit_is_set(n, 8) then result = result + P.OWNER_R end -- 0400
if bit_is_set(n, 7) then result = result + P.OWNER_W end -- 0200
if bit_is_set(n, 6) then result = result + P.OWNER_X end -- 0100
if bit_is_set(n, 5) then result = result + P.GROUP_R end -- 040
if bit_is_set(n, 4) then result = result + P.GROUP_W end -- 020
if bit_is_set(n, 3) then result = result + P.GROUP_X end -- 010
if bit_is_set(n, 2) then result = result + P.WORLD_R end -- 004
if bit_is_set(n, 1) then result = result + P.WORLD_W end -- 002
if bit_is_set(n, 0) then result = result + P.WORLD_X end -- 001
if bit_is_set(n, 11) then result = result + P.SUID end
return result
end
local function applySymbolic(modeStr, existingPerms)
local perms = existingPerms
for clause in (modeStr .. ","):gmatch("([^,]+),") do
local who_str, rest = clause:match("^([ugoa]*)([+%-=].+)$")
if not who_str then
print(name .. ": invalid mode: '" .. clause .. "'")
syscall.exit(1); return nil
end
if who_str == "" or who_str == "a" then who_str = "ugo" end
local op = rest:sub(1, 1)
local bits_str = rest:sub(2)
local mask = 0
for i = 1, #bits_str do
local c = bits_str:sub(i, i)
for j = 1, #who_str do
local w = who_str:sub(j, j)
if c == "r" then
if w == "u" then mask = mask + P.OWNER_R
elseif w == "g" then mask = mask + P.GROUP_R
elseif w == "o" then mask = mask + P.WORLD_R end
elseif c == "w" then
if w == "u" then mask = mask + P.OWNER_W
elseif w == "g" then mask = mask + P.GROUP_W
elseif w == "o" then mask = mask + P.WORLD_W end
elseif c == "x" then
if w == "u" then mask = mask + P.OWNER_X
elseif w == "g" then mask = mask + P.GROUP_X
elseif w == "o" then mask = mask + P.WORLD_X end
elseif c == "s" then
if w == "u" then mask = mask + P.SUID end
end
end
end
if op == "+" then
perms = perms + (mask - (perms % (mask + 1) - perms % mask > 0 and 0 or 0))
perms = perms - (perms % 1)
local function bor(a, b)
local result, bit = 0, 1
while a > 0 or b > 0 do
if (a % 2 == 1) or (b % 2 == 1) then result = result + bit end
a = math.floor(a / 2); b = math.floor(b / 2); bit = bit * 2
end
return result
end
perms = bor(perms, mask)
elseif op == "-" then
local function band(a, b)
local result, bit = 0, 1
while a > 0 and b > 0 do
if (a % 2 == 1) and (b % 2 == 1) then result = result + bit end
a = math.floor(a / 2); b = math.floor(b / 2); bit = bit * 2
end
return result
end
local function bxor(a, b)
local result, bit = 0, 1
while a > 0 or b > 0 do
if (a % 2 == 1) ~= (b % 2 == 1) then result = result + bit end
a = math.floor(a / 2); b = math.floor(b / 2); bit = bit * 2
end
return result
end
perms = bxor(perms, band(perms, mask))
elseif op == "=" then
local clearMask = 0
for j = 1, #who_str do
local w = who_str:sub(j, j)
if w == "u" then clearMask = clearMask + P.OWNER_R + P.OWNER_W + P.OWNER_X + P.SUID
elseif w == "g" then clearMask = clearMask + P.GROUP_R + P.GROUP_W + P.GROUP_X
elseif w == "o" then clearMask = clearMask + P.WORLD_R + P.WORLD_W + P.WORLD_X end
end
local function bxor(a, b)
local result, bit = 0, 1
while a > 0 or b > 0 do
if (a % 2 == 1) ~= (b % 2 == 1) then result = result + bit end
a = math.floor(a / 2); b = math.floor(b / 2); bit = bit * 2
end
return result
end
local function band(a, b)
local result, bit = 0, 1
while a > 0 and b > 0 do
if (a % 2 == 1) and (b % 2 == 1) then result = result + bit end
a = math.floor(a / 2); b = math.floor(b / 2); bit = bit * 2
end
return result
end
local function bor(a, b)
local result, bit = 0, 1
while a > 0 or b > 0 do
if (a % 2 == 1) or (b % 2 == 1) then result = result + bit end
a = math.floor(a / 2); b = math.floor(b / 2); bit = bit * 2
end
return result
end
perms = bxor(perms, band(perms, clearMask))
perms = bor(perms, mask)
else
print(name .. ": invalid operator in mode: '" .. clause .. "'")
syscall.exit(1); return nil
end
end
return perms
end
local function resolveMode(modeStr, existingPerms)
if modeStr:match("^[0-7]+$") then
local p = parseOctal(modeStr)
if p then return p end
end
return applySymbolic(modeStr, existingPerms)
end
local function chmodPath(path)
local stat, err = pcall(syscall.stat, path)
local existingPerms = 0
if stat then
local s = syscall.stat(path)
existingPerms = s and s.perms or 0
end
local newPerms = resolveMode(modeArg, existingPerms)
if newPerms == nil then return false end
local ok, cerr = pcall(syscall.chmod, path, newPerms)
if not ok then
local msg = tostring(cerr)
if msg:find("EACCES") or msg:find("EPERM") then
msg = "permission denied"
elseif msg:find("ENOENT") then
msg = "no such file or directory"
end
print(name .. ": cannot change permissions of '" .. path .. "': " .. msg)
return false
end
return true
end
local function chmodRecursive(path)
if not chmodPath(path) then return end
if syscall.type(path) == "directory" then
local ok, list = pcall(syscall.listdir, path)
if ok then
for _, entry in ipairs(list) do
local child = path
if child:sub(-1) ~= "/" then child = child .. "/" end
chmodRecursive(child .. entry)
end
end
end
end
local cwd = syscall.getcwd()
local function absPath(p)
if p:sub(1,1) ~= "/" then p = cwd .. "/" .. p end
return p
end
local exitCode = 0
for i = 2, #args do
local path = absPath(args[i])
if not syscall.exists(path) then
print(name .. ": cannot access '" .. args[i] .. "': No such file or directory")
exitCode = 1
elseif cloptions.R then
chmodRecursive(path)
else
if not chmodPath(path) then exitCode = 1 end
end
end
syscall.exit(exitCode)

150
Src/hysh/data/bin/chown Normal file
View File

@@ -0,0 +1,150 @@
--:Minify:--
local name = syscall.getTask(syscall.getpid()).name
local cloptions = { R = false, help = false }
local args = {}
for _, v in ipairs({ ... }) do
if v:sub(1, 2) == "--" then
local opt = v:sub(3)
if cloptions[opt] == nil then
print(name .. ": unrecognized option '" .. v .. "'")
print("try '" .. name .. " --help' for more information.")
syscall.exit(1); return
end
cloptions[opt] = true
elseif v:sub(1, 1) == "-" then
for i = 2, #v do
local opt = v:sub(i, i)
if cloptions[opt] == nil then
print(name .. ": invalid option '-" .. opt .. "'")
print("try '" .. name .. " --help' for more information.")
syscall.exit(1); return
end
cloptions[opt] = true
end
else
table.insert(args, v)
end
end
if cloptions.help then
print("Usage: " .. name .. " [OPTION]... OWNER[:GROUP] FILE...")
print(" " .. name .. " [OPTION]... :GROUP FILE...")
print("Change the owner and/or group of each FILE.")
print("OWNER and GROUP may be names or numeric IDs.")
print("")
print("Options:")
print(" -R operate on files and directories recursively")
print(" --help display this help and exit")
return
end
if #args < 2 then
print(name .. ": missing operand")
print("try '" .. name .. " --help' for more information.")
syscall.exit(1); return
end
local spec = args[1]
local ownerStr, groupStr
if spec:sub(1,1) == ":" then
groupStr = spec:sub(2)
else
local colon = spec:find(":", 1, true)
if colon then
ownerStr = spec:sub(1, colon - 1)
groupStr = spec:sub(colon + 1)
if groupStr == "" then groupStr = nil end
else
ownerStr = spec
end
end
local function resolveUid(s)
if not s or s == "" then return nil end
local n = tonumber(s)
if n then return n end
local uid = syscall.getuidbyname and syscall.getuidbyname(s)
if uid then return uid end
print(name .. ": invalid user: '" .. s .. "'")
syscall.exit(1)
end
local function resolveGid(s)
if not s or s == "" then return nil end
local n = tonumber(s)
if n then return n end
local uid = syscall.getuidbyname and syscall.getuidbyname(s)
if uid then
local pwent = syscall.getpasswd(uid)
if pwent then return pwent.gid end
end
print(name .. ": invalid group: '" .. s .. "'")
syscall.exit(1)
end
local newUid = resolveUid(ownerStr)
local newGid = resolveGid(groupStr)
if newUid == nil and newGid == nil then
print(name .. ": no owner or group specified")
syscall.exit(1); return
end
local function chownPath(path)
local stat = syscall.stat(path)
if not stat then
print(name .. ": cannot stat '" .. path .. "': no such file or directory")
return false
end
local uid = newUid ~= nil and newUid or stat.owner
local gid = newGid ~= nil and newGid or stat.group
local ok, err = pcall(syscall.chown, path, uid, gid)
if not ok then
local msg = tostring(err)
if msg:find("EPERM") or msg:find("EACCES") then
msg = "operation not permitted (must be root)"
elseif msg:find("ENOENT") then
msg = "no such file or directory"
end
print(name .. ": cannot change owner of '" .. path .. "': " .. msg)
return false
end
return true
end
local function chownRecursive(path)
if not chownPath(path) then return end
if syscall.type(path) == "directory" then
local ok, list = pcall(syscall.listdir, path)
if ok then
for _, entry in ipairs(list) do
local child = path
if child:sub(-1) ~= "/" then child = child .. "/" end
chownRecursive(child .. entry)
end
end
end
end
local cwd = syscall.getcwd()
local function absPath(p)
if p:sub(1,1) ~= "/" then p = cwd .. "/" .. p end
return p
end
local exitCode = 0
for i = 2, #args do
local path = absPath(args[i])
if not syscall.exists(path) then
print(name .. ": cannot access '" .. args[i] .. "': No such file or directory")
exitCode = 1
elseif cloptions.R then
chownRecursive(path)
else
if not chownPath(path) then exitCode = 1 end
end
end
syscall.exit(exitCode)

83
Src/hysh/data/bin/chroot Normal file
View File

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

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

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

@@ -0,0 +1,100 @@
--:Minify:--
-- Color indices (sfgc):
-- 1=white, 2=red, 3=green, 4=blue, 5=cyan, 6=magenta, 7=yellow
-- 8=orange, 9=lime, 10=lightcyan, 11=brown, 12=darkgrey, 13=lightgrey, 14=purple, 15=chartreuse, 16=black
local C_LOGO = 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

File diff suppressed because it is too large Load Diff

19
Src/hysh/data/bin/id Normal file
View File

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

3
Src/hysh/data/bin/ll Normal file
View File

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

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

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

169
Src/hysh/data/bin/login Normal file
View 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

View File

@@ -0,0 +1,157 @@
--:Minify:--
-- Usage:
-- loimgcreate <srcdir> <image.hfs> create image from directory
-- loimgcreate -x <image.hfs> <dest> extract image back to a directory
-- loimgcreate --help
local name = syscall.getTask(syscall.getpid()).name
local args, opts = {}, { x=false, help=false }
for _, v in ipairs({...}) do
if v:sub(1,2) == "--" then
local o = v:sub(3)
if o == "help" then opts.help = true
else print(name..": unrecognised option '"..v.."'")
print("try '"..name.." --help' for more information.")
syscall.exit(1); return end
elseif v:sub(1,1) == "-" then
for i = 2, #v do
local c = v:sub(i,i)
if opts[c] ~= nil then opts[c] = true
else print(name..": invalid option '-"..c.."'")
print("try '"..name.." --help' for more information.")
syscall.exit(1); return end
end
else
table.insert(args, v)
end
end
if opts.help then
print("Usage: "..name.." <srcdir> <image.hfs>")
print(" "..name.." -x <image.hfs> <destdir>")
print("")
print("Pack a directory into a portable HFS image file, or extract one.")
print("")
print(" <srcdir> <image.hfs> recursively pack srcdir into image.hfs")
print(" -x <image.hfs> <dest> extract image.hfs into dest (created if needed)")
print("")
print("HFS images can be mounted with:")
print(" mount -o loop /path/to/image.hfs /mnt/point")
print("")
print("Requires root.")
return
end
local fs = require("fs")
if opts.x then
if #args < 2 then
print(name..": -x requires <image.hfs> and <destdir>")
print("try '"..name.." --help' for more information.")
syscall.exit(1); return
end
local imgPath = args[1]
local destPath = args[2]
if imgPath:sub(1,1) ~= "/" then imgPath = syscall.getcwd().."/"..imgPath end
if destPath:sub(1,1) ~= "/" then destPath = syscall.getcwd().."/"..destPath end
local tmpMnt = "/tmp/._loimgcreate_"..tostring(math.random(100000,999999))
local ok1, loopId = pcall(syscall.losetup, imgPath, true)
if not ok1 then
print(name..": losetup: "..tostring(loopId)); syscall.exit(1); return
end
local ok2, merr = pcall(syscall.mount, tmpMnt, loopId)
if not ok2 then
pcall(syscall.lodetach, loopId)
print(name..": mount: "..tostring(merr)); syscall.exit(1); return
end
if not fs.isDir(destPath) then
local ok3, derr = pcall(syscall.mkdir, destPath)
if not ok3 then
pcall(syscall.umount, tmpMnt); pcall(syscall.lodetach, loopId)
print(name..": mkdir '"..args[2].."': "..tostring(derr))
syscall.exit(1); return
end
end
local count = 0
local function copyTree(src, dst)
local entries = fs.list(src)
if not entries then return end
for _, ent in ipairs(entries) do
local srcFull = src:gsub("/$","").."/"..ent
local dstFull = dst:gsub("/$","").."/"..ent
if fs.isDir(srcFull) then
pcall(syscall.mkdir, dstFull)
copyTree(srcFull, dstFull)
else
local ok, rfd = pcall(syscall.open, srcFull, "r")
if ok then
local ok2, wfd = pcall(syscall.open, dstFull, "w")
if ok2 then
local ok3, data = pcall(syscall.read, rfd, 65536*16)
if ok3 and data then pcall(syscall.write, wfd, data) end
pcall(syscall.close, wfd)
count = count + 1
end
pcall(syscall.close, rfd)
end
end
end
end
copyTree(tmpMnt, destPath)
pcall(syscall.umount, tmpMnt)
pcall(syscall.lodetach, loopId)
syscall.devctl(1, "sfgc", 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
View File

@@ -0,0 +1,129 @@
--:Minify:--
-- Usage:
-- losetup <path> attach directory or .hfs image; print loop id
-- losetup -d <id> detach loop device
-- losetup -l list attached loop devices
-- losetup -i <path> force image mode (even without .hfs extension)
-- losetup --help
local name = syscall.getTask(syscall.getpid()).name
local args, opts = {}, { d=false, l=false, i=false, help=false }
for _, v in ipairs({...}) do
if v:sub(1,2) == "--" then
local o = v:sub(3)
if o == "help" then opts.help = true
else print(name..": unrecognised option '"..v.."'")
print("try '"..name.." --help' for more information.")
syscall.exit(1); return end
elseif v:sub(1,1) == "-" then
for i = 2, #v do
local c = v:sub(i,i)
if opts[c] ~= nil then opts[c] = true
else print(name..": invalid option '-"..c.."'")
print("try '"..name.." --help' for more information.")
syscall.exit(1); return end
end
else
table.insert(args, v)
end
end
if opts.help then
print("Usage: "..name.." <path>")
print(" "..name.." -i <path>")
print(" "..name.." -d <id>")
print(" "..name.." -l")
print("")
print("Manage loop devices.")
print("")
print(" <path> attach a directory (bind) or .hfs image file")
print(" -i <path> force image mode for the given file")
print(" -d <id> detach loop device by id (must be unmounted first)")
print(" -l list all currently attached loop devices")
print("")
print("Requires root. Loop device ids look like loop0, loop1, …")
return
end
if opts.l then
local ok, devs = pcall(syscall.lolist)
if not ok then
print(name..": "..tostring(devs)); syscall.exit(1); return
end
local any = false
local ids = {}
for id in pairs(devs) do ids[#ids+1] = id end
table.sort(ids)
for _, id in ipairs(ids) do
any = true
local info = devs[id]
local mode = (type(info) == "table" and info.mode) or "bind"
local path = (type(info) == "table" and info.path) or tostring(info)
local colour = mode == "image" and 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
View 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
View File

@@ -0,0 +1,19 @@
--:Minify:--
local users = syscall.listusers()
if not users or #users == 0 then
print("No users found.")
return
end
syscall.devctl(1,"sfgc",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
View File

@@ -0,0 +1,152 @@
--:Minify:--
-- Usage:
-- mount list all current mounts
-- mount <id> <mountpoint> mount loop device id at mountpoint
-- mount -o loop <src> <dest> attach <src> as loop device and mount at <dest>
-- mount --help
local name = syscall.getTask(syscall.getpid()).name
local args, opts = {}, { help=false, o=nil }
local i = 1
local rawArgs = {...}
while i <= #rawArgs do
local v = rawArgs[i]
if v:sub(1,2) == "--" then
local o = v:sub(3)
if o == "help" then opts.help = true
else print(name..": unrecognised option '"..v.."'")
print("try '"..name.." --help' for more information.")
syscall.exit(1); return end
elseif v == "-o" then
i = i + 1
opts.o = rawArgs[i]
elseif v:sub(1,1) == "-" then
local rest = v:sub(2)
if rest:sub(1,1) == "o" then
if #rest > 1 then opts.o = rest:sub(2)
else i = i + 1; opts.o = rawArgs[i] end
else
print(name..": invalid option '"..v.."'")
print("try '"..name.." --help' for more information.")
syscall.exit(1); return
end
else
table.insert(args, v)
end
i = i + 1
end
if opts.help then
print("Usage: "..name)
print(" "..name.." <id> <mountpoint>")
print(" "..name.." -o loop <source> <mountpoint>")
print("")
print("Mount a loop device or filesystem.")
print("")
print(" (no args) list all active mount points")
print(" <id> <mountpoint> mount an already-attached loop device")
print(" -o loop <src> <dest> attach src as loop device and mount at dest")
print(" src can be a directory (bind) or .hfs image")
print("")
print("Requires root for all operations except listing.")
return
end
if #args == 0 and not opts.o then
local ok, mounts = pcall(syscall.mounts or function()
error("ENOSYS")
end)
local loDevs = {}
local lok, ld = pcall(syscall.lolist)
if lok then
for id, info in pairs(ld) do
local path = (type(info)=="table" and info.path) or tostring(info)
local mode = (type(info)=="table" and info.mode) or "bind"
loDevs[id] = { path=path, mode=mode }
end
end
if next(loDevs) == nil then
syscall.devctl(1, "sfgc", 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
View File

@@ -0,0 +1,80 @@
--:Minify:--
-- passwd: change a user's password
-- Usage: passwd [username] (default: current user)
local args = {...}
local targetName = args[1]
local currentUid = syscall.getuid()
local targetUid
if targetName then
targetUid = syscall.getuid()
if not targetUid then
print("passwd: user '" .. targetName .. "' does not exist")
syscall.exit(1); return
end
-- Only root can change another user's password
if currentUid ~= 0 and targetUid ~= currentUid then
print("passwd: permission denied")
syscall.exit(1); return
end
else
targetUid = currentUid
targetName = syscall.getUsername(currentUid) or tostring(currentUid)
end
-- Non-root must verify their current password first
if currentUid ~= 0 then
printInline("Current password: ")
local cur = ""
while true do
local ch = syscall.read(0)
if not ch or ch == "" then
elseif ch == "\n" then syscall.write(1,"\n"); break
elseif ch == "\b" then
if #cur > 0 then cur=cur:sub(1,-2); syscall.write(1,"\b \b") end
else cur=cur..ch; syscall.write(1,"*") end
end
local ok, err = syscall.login(targetUid, cur)
if not ok then
sleep(1)
print("passwd: authentication failure")
syscall.exit(1); return
end
end
printInline("New password: ")
local pw1 = ""
while true do
local ch = syscall.read(0)
if not ch or ch == "" then
elseif ch == "\n" then syscall.write(1,"\n"); break
elseif ch == "\b" then
if #pw1 > 0 then pw1=pw1:sub(1,-2); syscall.write(1,"\b \b") end
else pw1=pw1..ch; syscall.write(1,"*") end
end
printInline("Confirm password: ")
local pw2 = ""
while true do
local ch = syscall.read(0)
if not ch or ch == "" then
elseif ch == "\n" then syscall.write(1,"\n"); break
elseif ch == "\b" then
if #pw2 > 0 then pw2=pw2:sub(1,-2); syscall.write(1,"\b \b") end
else pw2=pw2..ch; syscall.write(1,"*") end
end
if pw1 ~= pw2 then
print("passwd: passwords do not match")
syscall.exit(1); return
end
local ok, err = syscall.setpassword(targetUid, pw1)
if not ok then
print("passwd: " .. tostring(err))
syscall.exit(1); return
end
print("passwd: password updated for '" .. targetName .. "'")

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

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

View File

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

425
Src/hysh/data/bin/sed Normal file
View 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
View 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
View File

@@ -0,0 +1,110 @@
--:Minify:--
local fs = require("fs")
local cmdArgs = {...}
local targetUser = "root"
local i = 1
if cmdArgs[i] == "-u" then
i = i + 1
local uarg = cmdArgs[i] or "root"
local numUid = tonumber(uarg)
if numUid then
local pwent = syscall.getpasswd(numUid)
targetUser = (pwent and pwent.username) or uarg
else
targetUser = uarg
end
i = i + 1
end
local cmd = cmdArgs[i]
if not cmd or cmd == "" then
print("usage: sudo [-u user] <command> [args...]")
syscall.exit(1)
return
end
local restArgs = {}
for j = i + 1, #cmdArgs do restArgs[#restArgs + 1] = cmdArgs[j] end
local currentUid = syscall.getuid()
local currentUser = syscall.getUsername(currentUid) or tostring(currentUid)
local targetUid = syscall.getuidbyname(targetUser)
if not targetUid then
print("sudo: user '" .. targetUser .. "' does not exist")
syscall.exit(1)
return
end
if currentUid ~= 0 then
printInline("[sudo] password for root: ")
local pw = ""
while true do
local ch = syscall.read(0)
if not ch or ch == "" then
elseif ch == "\n" then
syscall.write(1, "\n")
break
elseif ch == "\b" then
if #pw > 0 then pw = pw:sub(1, -2); syscall.write(1, "\b \b") end
else
pw = pw .. ch
syscall.write(1, "*")
end
end
local ok, err = syscall.login(0, pw)
if not ok then
sleep(1)
print("sudo: Authentication failure")
syscall.exit(1)
return
end
if targetUid ~= currentUid then
syscall.setuid(targetUid)
end
else
if targetUid ~= currentUid then
syscall.setuid(targetUid)
end
end
local cmdPath = ""
if cmd:find("/") then
if fs.exists(cmd) then cmdPath = cmd end
else
local paths = string.split(syscall.getEnviron("PATH") or "/bin/", ":")
for _, p in ipairs(paths) do
local full = p .. cmd
if fs.exists(full) then cmdPath = full; break end
end
end
if cmdPath == "" then
print("sudo: command not found: " .. cmd)
syscall.exit(1)
return
end
local text = fs.readAllText(cmdPath)
local program, loadErr = load(text, "@" .. cmdPath)
if not program then
print("sudo: cannot load " .. cmd .. ": " .. tostring(loadErr))
syscall.exit(1)
return
end
local pwent = syscall.getpasswd(targetUid)
if pwent and pwent.homedir then
syscall.setEnviron("HOME", pwent.homedir)
end
syscall.setEnviron("USER", targetUser)
local ok, err = xpcall(program, debug.traceback, table.unpack(restArgs))
if not ok then
print("sudo: " .. cmd .. ": " .. tostring(err))
syscall.exit(1)
end

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

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

111
Src/hysh/data/bin/umount Normal file
View File

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

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

49
Src/hysh/data/bin/userdel Normal file
View File

@@ -0,0 +1,49 @@
--:Minify:--
local args = {...}
local removeHome = false
local username = nil
for _, a in ipairs(args) do
if a == "-r" then removeHome = true
elseif a:sub(1,1) ~= "-" then username = a
else print("userdel: unknown option: " .. a); syscall.exit(1); return end
end
if not username then
print("Usage: userdel [-r] <username>")
syscall.exit(1); return
end
local uid = syscall.getuid(username)
if not uid then
print("userdel: user '" .. username .. "' does not exist")
syscall.exit(1); return
end
local pwent = syscall.getpasswd(uid)
local ok, err = syscall.deleteuser(uid)
if not ok then
print("userdel: " .. tostring(err))
syscall.exit(1); return
end
if removeHome and pwent and pwent.homedir then
local fs = require("fs")
local ok2, err2 = pcall(function()
local function rmdir(path)
for _, f in ipairs(fs.list(path) or {}) do
local full = path .. "/" .. f
if fs.isDir(full) then rmdir(full)
else syscall.remove(full) end
end
syscall.remove(path)
end
if fs.exists(pwent.homedir) then rmdir(pwent.homedir) end
end)
if not ok2 then
print("userdel: warning: could not remove home: " .. tostring(err2))
end
end
print("userdel: deleted user '" .. username .. "'")

49
Src/hysh/data/bin/usermod Normal file
View File

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

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

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