Минималистичная версия. 1 юзер, базовая fs, man по командам хардкодом
This commit is contained in:
183
atlas_os/CMC.lua
183
atlas_os/CMC.lua
@@ -8,7 +8,8 @@ local ST_IDLE = 0
|
|||||||
local ST_WAIT_FS = 1
|
local ST_WAIT_FS = 1
|
||||||
local state = ST_IDLE
|
local state = ST_IDLE
|
||||||
|
|
||||||
local uid, gid, cwd, curUser = 0, 0, "/", "root"
|
local uid, gid, curUser = 0, 0, "root"
|
||||||
|
local cwd = curUser == "root" and "/" or "/home/" .. curUser
|
||||||
local lastRespSeq = 0
|
local lastRespSeq = 0
|
||||||
|
|
||||||
local outQueue = {}
|
local outQueue = {}
|
||||||
@@ -19,11 +20,34 @@ local function parseMsg(msg)
|
|||||||
return p and msg:sub(1, p - 1) or msg, p and msg:sub(p + 1) or nil
|
return p and msg:sub(1, p - 1) or msg, p and msg:sub(p + 1) or nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function normalizePath(path)
|
||||||
|
local parts = {}
|
||||||
|
for seg in path:gmatch("[^/]+") do
|
||||||
|
if seg == ".." then
|
||||||
|
if #parts > 0 then table.remove(parts) end
|
||||||
|
elseif seg ~= "." then
|
||||||
|
parts[#parts + 1] = seg
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if #parts == 0 then return "/" end
|
||||||
|
return "/" .. table.concat(parts, "/")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function expandHome(path)
|
||||||
|
local home = curUser == "root" and "/" or "/home/" .. curUser
|
||||||
|
local rest = path:sub(2)
|
||||||
|
if rest == "" or rest:sub(1, 1) == "/" then
|
||||||
|
return normalizePath(home .. rest)
|
||||||
|
end
|
||||||
|
return normalizePath(home .. "/" .. rest)
|
||||||
|
end
|
||||||
|
|
||||||
local function resolvePath(path)
|
local function resolvePath(path)
|
||||||
if not path or path == "" then return cwd end
|
if not path or path == "" then return cwd end
|
||||||
if path:sub(1, 1) == "/" then return path end
|
if path:sub(1, 1) == "~" then return expandHome(path) end
|
||||||
if cwd:sub(-1) == "/" then return cwd .. path end
|
if path:sub(1, 1) == "/" then return normalizePath(path) end
|
||||||
return cwd .. "/" .. path
|
local full = cwd .. (cwd:sub(-1) == "/" and "" or "/") .. path
|
||||||
|
return normalizePath(full)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function fsReq(op, arg)
|
local function fsReq(op, arg)
|
||||||
@@ -55,46 +79,78 @@ local function queueLines(lines)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- centralized argument validation
|
||||||
|
local valid = {}
|
||||||
|
|
||||||
|
function valid.minArgs(args, n, msg)
|
||||||
|
if #args < n then error("usage: " .. msg) end
|
||||||
|
end
|
||||||
|
|
||||||
|
function valid.maxArgs(args, n, msg)
|
||||||
|
if #args > n then error("usage: " .. msg) end
|
||||||
|
end
|
||||||
|
|
||||||
|
function valid.exactArgs(args, n, msg)
|
||||||
|
if #args ~= n then error("usage: " .. msg) end
|
||||||
|
end
|
||||||
|
|
||||||
|
function valid.num(v, name)
|
||||||
|
local n = tonumber(v)
|
||||||
|
if not n then error(name .. ": expected number, got '" .. tostring(v) .. "'") end
|
||||||
|
return math.floor(math.max(0, math.min(255, n)))
|
||||||
|
end
|
||||||
|
|
||||||
|
function valid.flags(args, allowed)
|
||||||
|
local opts, positional = {}, nil
|
||||||
|
for _, arg in ipairs(args) do
|
||||||
|
if arg:sub(1, 1) == "-" then
|
||||||
|
for j = 2, #arg do
|
||||||
|
local c = arg:sub(j, j)
|
||||||
|
if not allowed[c] then error("unknown option: -" .. c) end
|
||||||
|
opts[c] = true
|
||||||
|
end
|
||||||
|
else
|
||||||
|
positional = arg
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return opts, positional
|
||||||
|
end
|
||||||
|
|
||||||
local cmds = {}
|
local cmds = {}
|
||||||
|
|
||||||
local function reg(name, fn, desc)
|
local function reg(name, fn, desc, usage)
|
||||||
cmds[name] = { fn = fn, desc = desc }
|
cmds[name] = { fn = fn, desc = desc, usage = usage }
|
||||||
end
|
end
|
||||||
|
|
||||||
reg("help", function(a)
|
reg("help", function(a)
|
||||||
local lines = { curUser .. " " .. cwd .. "> help" }
|
local lines = { curUser .. " " .. cwd .. "> help" }
|
||||||
lines[#lines + 1] = "AtlasOS v2.0:"
|
lines[#lines + 1] = "AtlasOS v2.0:"
|
||||||
for n, e in pairs(cmds) do
|
for n, e in pairs(cmds) do
|
||||||
lines[#lines + 1] = " " .. n .. " " .. e.desc
|
local u = e.usage and " " .. e.usage or ""
|
||||||
|
lines[#lines + 1] = " " .. n .. u .. " " .. e.desc
|
||||||
end
|
end
|
||||||
queueLines(lines)
|
queueLines(lines)
|
||||||
end, "This help")
|
end, "This help")
|
||||||
|
|
||||||
reg("echo", function(a)
|
reg("echo", function(a)
|
||||||
if #a == 0 then error("echo: missing text") end
|
valid.minArgs(a, 1, "echo <text>")
|
||||||
local out = ""
|
local out = table.concat(a, " ")
|
||||||
for i = 1, #a do
|
|
||||||
if #out > 0 then out = out .. " " end
|
|
||||||
out = out .. tostring(a[i])
|
|
||||||
end
|
|
||||||
queueLines({ curUser .. " " .. cwd .. "> echo " .. out, out })
|
queueLines({ curUser .. " " .. cwd .. "> echo " .. out, out })
|
||||||
end, "Echo text")
|
end, "Echo text", "<text>")
|
||||||
|
|
||||||
reg("clear", function(a)
|
reg("clear", function(a)
|
||||||
qClear()
|
qClear()
|
||||||
end, "Clear screen")
|
end, "Clear screen")
|
||||||
|
|
||||||
reg("color", function(a)
|
reg("color", function(a)
|
||||||
if #a < 3 then error("color: need R G B") end
|
valid.exactArgs(a, 3, "color <R> <G> <B>")
|
||||||
local r, g, b = tonumber(a[1]), tonumber(a[2]), tonumber(a[3])
|
local r = valid.num(a[1], "color")
|
||||||
if not (r and g and b) then error("color: invalid values") end
|
local g = valid.num(a[2], "color")
|
||||||
r = math.floor(math.max(0, math.min(255, r)))
|
local b = valid.num(a[3], "color")
|
||||||
g = math.floor(math.max(0, math.min(255, g)))
|
|
||||||
b = math.floor(math.max(0, math.min(255, b)))
|
|
||||||
qColor(r, g, b)
|
qColor(r, g, b)
|
||||||
queueLines({ curUser .. " " .. cwd .. "> color",
|
queueLines({ curUser .. " " .. cwd .. "> color",
|
||||||
"Color set to " .. r .. "," .. g .. "," .. b })
|
"Color set to " .. r .. "," .. g .. "," .. b })
|
||||||
end, "Set color R G B")
|
end, "Set color", "<R> <G> <B>")
|
||||||
|
|
||||||
reg("status", function(a)
|
reg("status", function(a)
|
||||||
pendingCmd = { name = "status",
|
pendingCmd = { name = "status",
|
||||||
@@ -104,57 +160,51 @@ reg("status", function(a)
|
|||||||
end, "System status")
|
end, "System status")
|
||||||
|
|
||||||
reg("ls", function(a)
|
reg("ls", function(a)
|
||||||
local path = cwd
|
local opts, path = valid.flags(a, { a = true, l = true })
|
||||||
local opts = {}
|
path = resolvePath(path or cwd)
|
||||||
for i = 1, #a do
|
|
||||||
if a[i]:sub(1, 1) == "-" then
|
|
||||||
local opt = a[i]
|
|
||||||
for j = 2, #opt do
|
|
||||||
local c = opt:sub(j, j)
|
|
||||||
if c ~= "a" and c ~= "l" then
|
|
||||||
error("ls: unknown option '" .. opt .. "'")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
opts[opt] = true
|
|
||||||
else
|
|
||||||
path = resolvePath(a[i])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
local cmdStr = "ls"
|
local cmdStr = "ls"
|
||||||
for i = 1, #a do
|
for i = 1, #a do cmdStr = cmdStr .. " " .. tostring(a[i]) end
|
||||||
cmdStr = cmdStr .. " " .. tostring(a[i])
|
|
||||||
end
|
|
||||||
pendingCmd = { name = "ls", opts = opts, path = path,
|
pendingCmd = { name = "ls", opts = opts, path = path,
|
||||||
prompt = curUser .. " " .. cwd .. "> " .. cmdStr }
|
prompt = curUser .. " " .. cwd .. "> " .. cmdStr }
|
||||||
state = ST_WAIT_FS
|
state = ST_WAIT_FS
|
||||||
fsReq("LS", path)
|
fsReq("LS", path)
|
||||||
end, "List directory [path]")
|
end, "List directory", "[-la] [path]")
|
||||||
|
|
||||||
reg("cat", function(a)
|
reg("cat", function(a)
|
||||||
if #a == 0 then error("cat: need path") end
|
valid.minArgs(a, 1, "cat <path>")
|
||||||
local path = resolvePath(a[1])
|
local path = resolvePath(a[1])
|
||||||
pendingCmd = { name = "cat", path = path,
|
pendingCmd = { name = "cat", path = path,
|
||||||
prompt = curUser .. " " .. cwd .. "> cat " .. a[1] }
|
prompt = curUser .. " " .. cwd .. "> cat " .. a[1] }
|
||||||
state = ST_WAIT_FS
|
state = ST_WAIT_FS
|
||||||
fsReq("READ", path .. "|0|8192")
|
fsReq("READ", path .. "|0|8192")
|
||||||
end, "Show file content")
|
end, "Show file content", "<path>")
|
||||||
|
|
||||||
reg("cd", function(a)
|
reg("cd", function(a)
|
||||||
|
valid.maxArgs(a, 1, "cd [path]")
|
||||||
local path = a[1] and resolvePath(a[1]) or "/"
|
local path = a[1] and resolvePath(a[1]) or "/"
|
||||||
pendingCmd = { name = "cd", path = path,
|
pendingCmd = { name = "cd", path = path,
|
||||||
prompt = curUser .. " " .. cwd .. "> cd " .. (a[1] or "") }
|
prompt = curUser .. " " .. cwd .. "> cd " .. (a[1] or "") }
|
||||||
state = ST_WAIT_FS
|
state = ST_WAIT_FS
|
||||||
fsReq("CHDIR", path)
|
fsReq("CHDIR", path)
|
||||||
end, "Change directory [path]")
|
end, "Change directory", "[path]")
|
||||||
|
|
||||||
reg("stat", function(a)
|
reg("stat", function(a)
|
||||||
if #a == 0 then error("stat: need path") end
|
valid.minArgs(a, 1, "stat <path>")
|
||||||
local path = resolvePath(a[1])
|
local path = resolvePath(a[1])
|
||||||
pendingCmd = { name = "stat", path = path,
|
pendingCmd = { name = "stat", path = path,
|
||||||
prompt = curUser .. " " .. cwd .. "> stat " .. a[1] }
|
prompt = curUser .. " " .. cwd .. "> stat " .. a[1] }
|
||||||
state = ST_WAIT_FS
|
state = ST_WAIT_FS
|
||||||
fsReq("STAT", path)
|
fsReq("STAT", path)
|
||||||
end, "Show file metadata")
|
end, "Show file metadata", "<path>")
|
||||||
|
|
||||||
|
reg("man", function(a)
|
||||||
|
valid.exactArgs(a, 1, "man <command>")
|
||||||
|
local cmd = a[1]
|
||||||
|
pendingCmd = { name = "man", cmd = cmd,
|
||||||
|
prompt = curUser .. " " .. cwd .. "> man " .. cmd }
|
||||||
|
state = ST_WAIT_FS
|
||||||
|
fsReq("READ", "/etc/man/" .. cmd .. "|0|8192")
|
||||||
|
end, "Display manual page", "<command>")
|
||||||
|
|
||||||
local function execCmd(cmdStr)
|
local function execCmd(cmdStr)
|
||||||
local parts = {}
|
local parts = {}
|
||||||
@@ -189,20 +239,24 @@ local function handleFSResponse(op, data)
|
|||||||
|
|
||||||
if op == "ERR" then
|
if op == "ERR" then
|
||||||
local code = data or "EIO"
|
local code = data or "EIO"
|
||||||
local m = cmd.path
|
|
||||||
if code == "ENOENT" then
|
|
||||||
m = m .. ": No such file or directory"
|
|
||||||
elseif code == "EACCES" then
|
|
||||||
m = m .. ": Permission denied"
|
|
||||||
elseif code == "ENOTDIR" then
|
|
||||||
m = m .. ": Not a directory"
|
|
||||||
elseif code == "EISDIR" then
|
|
||||||
m = m .. ": Is a directory"
|
|
||||||
else
|
|
||||||
m = m .. ": " .. code
|
|
||||||
end
|
|
||||||
qText(cmd.prompt)
|
qText(cmd.prompt)
|
||||||
qErr(m)
|
if cmd.name == "man" then
|
||||||
|
qErr("No manual entry for " .. cmd.cmd)
|
||||||
|
else
|
||||||
|
local m = cmd.path
|
||||||
|
if code == "ENOENT" then
|
||||||
|
m = m .. ": No such file or directory"
|
||||||
|
elseif code == "EACCES" then
|
||||||
|
m = m .. ": Permission denied"
|
||||||
|
elseif code == "ENOTDIR" then
|
||||||
|
m = m .. ": Not a directory"
|
||||||
|
elseif code == "EISDIR" then
|
||||||
|
m = m .. ": Is a directory"
|
||||||
|
else
|
||||||
|
m = m .. ": " .. code
|
||||||
|
end
|
||||||
|
qErr(m)
|
||||||
|
end
|
||||||
pendingCmd = nil
|
pendingCmd = nil
|
||||||
state = ST_IDLE
|
state = ST_IDLE
|
||||||
return
|
return
|
||||||
@@ -211,7 +265,7 @@ local function handleFSResponse(op, data)
|
|||||||
local lines = { cmd.prompt }
|
local lines = { cmd.prompt }
|
||||||
|
|
||||||
if cmd.name == "ls" then
|
if cmd.name == "ls" then
|
||||||
local showAll = (cmd.opts["-la"] or cmd.opts["-a"])
|
local showAll = cmd.opts["a"] or false
|
||||||
local names = {}
|
local names = {}
|
||||||
for n in data:gmatch("([^|]+)") do
|
for n in data:gmatch("([^|]+)") do
|
||||||
names[#names + 1] = n
|
names[#names + 1] = n
|
||||||
@@ -260,6 +314,15 @@ local function handleFSResponse(op, data)
|
|||||||
else
|
else
|
||||||
lines[#lines + 1] = data
|
lines[#lines + 1] = data
|
||||||
end
|
end
|
||||||
|
|
||||||
|
elseif cmd.name == "man" then
|
||||||
|
if data and #data > 0 then
|
||||||
|
for line in data:gmatch("[^\n]+") do
|
||||||
|
lines[#lines + 1] = line
|
||||||
|
end
|
||||||
|
else
|
||||||
|
lines[#lines + 1] = "(no content)"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
queueLines(lines)
|
queueLines(lines)
|
||||||
|
|||||||
142
atlas_os/MMC.lua
142
atlas_os/MMC.lua
@@ -233,7 +233,147 @@ local function defaultFS()
|
|||||||
mkFile(3, "Welcome to AtlasOS v2.0!\n")
|
mkFile(3, "Welcome to AtlasOS v2.0!\n")
|
||||||
|
|
||||||
mkIno(4, "d", "755", 0, 0, 0)
|
mkIno(4, "d", "755", 0, 0, 0)
|
||||||
mkDir(4, { { name = ".", ino = 4 }, { name = "..", ino = 1 } })
|
mkDir(4, { { name = ".", ino = 4 }, { name = "..", ino = 1 }, { name = "man", ino = 6 } })
|
||||||
|
|
||||||
|
mkIno(6, "d", "755", 0, 0, 0)
|
||||||
|
mkDir(6, {
|
||||||
|
{ name = ".", ino = 6 }, { name = "..", ino = 4 },
|
||||||
|
{ name = "help", ino = 7 }, { name = "echo", ino = 8 },
|
||||||
|
{ name = "clear", ino = 9 }, { name = "color", ino = 10 },
|
||||||
|
{ name = "status", ino = 11 }, { name = "ls", ino = 12 },
|
||||||
|
{ name = "cat", ino = 13 }, { name = "cd", ino = 14 },
|
||||||
|
{ name = "stat", ino = 15 }, { name = "man", ino = 16 },
|
||||||
|
})
|
||||||
|
|
||||||
|
mkIno(7, "f", "644", 0, 0, 0)
|
||||||
|
mkFile(7, [[NAME
|
||||||
|
help - display help information
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
help
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
Show a list of all available commands with their usage
|
||||||
|
and description.
|
||||||
|
]])
|
||||||
|
|
||||||
|
mkIno(8, "f", "644", 0, 0, 0)
|
||||||
|
mkFile(8, [[NAME
|
||||||
|
echo - display a line of text
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
echo <text>
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
Write the given text to the terminal output.
|
||||||
|
]])
|
||||||
|
|
||||||
|
mkIno(9, "f", "644", 0, 0, 0)
|
||||||
|
mkFile(9, [[NAME
|
||||||
|
clear - clear the terminal screen
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
clear
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
Clear all text from the terminal display.
|
||||||
|
]])
|
||||||
|
|
||||||
|
mkIno(10, "f", "644", 0, 0, 0)
|
||||||
|
mkFile(10, [[NAME
|
||||||
|
color - set terminal text color
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
color <R> <G> <B>
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
Change the text color for subsequent output.
|
||||||
|
Each value must be 0-255 (red, green, blue).
|
||||||
|
]])
|
||||||
|
|
||||||
|
mkIno(11, "f", "644", 0, 0, 0)
|
||||||
|
mkFile(11, [[NAME
|
||||||
|
status - show system status
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
status
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
Display system information including current user,
|
||||||
|
working directory, and memory usage.
|
||||||
|
]])
|
||||||
|
|
||||||
|
mkIno(12, "f", "644", 0, 0, 0)
|
||||||
|
mkFile(12, [[NAME
|
||||||
|
ls - list directory contents
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
ls [-la] [path]
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
List contents of a directory. If no path is given,
|
||||||
|
list the current directory.
|
||||||
|
|
||||||
|
OPTIONS
|
||||||
|
-a Include hidden entries (. and ..)
|
||||||
|
-l Long format (detailed listing)
|
||||||
|
]])
|
||||||
|
|
||||||
|
mkIno(13, "f", "644", 0, 0, 0)
|
||||||
|
mkFile(13, [[NAME
|
||||||
|
cat - concatenate and display files
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
cat <path>
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
Read a file and display its contents on the terminal.
|
||||||
|
]])
|
||||||
|
|
||||||
|
mkIno(14, "f", "644", 0, 0, 0)
|
||||||
|
mkFile(14, [[NAME
|
||||||
|
cd - change the working directory
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
cd [path]
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
Change the current working directory. If no path
|
||||||
|
is given, go to root (/). Supports both absolute
|
||||||
|
and relative paths.
|
||||||
|
]])
|
||||||
|
|
||||||
|
mkIno(15, "f", "644", 0, 0, 0)
|
||||||
|
mkFile(15, [[NAME
|
||||||
|
stat - show file metadata
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
stat <path>
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
Display metadata for a file or directory, including
|
||||||
|
inode number, type, permissions, owner, group, size,
|
||||||
|
and modification time.
|
||||||
|
]])
|
||||||
|
|
||||||
|
mkIno(16, "f", "644", 0, 0, 0)
|
||||||
|
mkFile(16, [[NAME
|
||||||
|
man - display manual page
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
man <command>
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
Display the manual page for a command. Manual pages
|
||||||
|
are stored in /etc/man/.
|
||||||
|
|
||||||
|
EXAMPLE
|
||||||
|
man ls
|
||||||
|
man cat
|
||||||
|
|
||||||
|
SEE ALSO
|
||||||
|
help
|
||||||
|
]])
|
||||||
|
|
||||||
local str = fsSerialize()
|
local str = fsSerialize()
|
||||||
out[5] = str
|
out[5] = str
|
||||||
|
|||||||
Reference in New Issue
Block a user