-- MMC.lua — Memory/FS Controller (AtlasOS v2.0) -- in[1] = mem_rx ← CMC.out[4] (FS request) -- in[3] = mem_read ← Memory.out[1] -- out[1] = mem_tx → CMC.in[2] (FS response) -- out[5] = mem_data → Memory.in[1] -- out[6] = mem_write → Memory.in[2] local SEG_SIZE = 8192 local MAX_INODES = 64 local ROOT_INO = 1 local function parseMsg(msg) local p = msg:find("|") return p and msg:sub(1, p - 1) or msg, p and msg:sub(p + 1) or nil end local function split(str, sep) local t = {} if not str then return t end for s in str:gmatch("[^" .. sep .. "]+") do t[#t + 1] = s end return t end local inodes = {} local dirs = {} local files = {} local users = {} local sb = { seg_size = SEG_SIZE, max_inodes = MAX_INODES, root_ino = ROOT_INO } local function fsSerialize() local lines = {} lines[#lines + 1] = "V|1" lines[#lines + 1] = "S|" .. sb.seg_size .. "|" .. sb.max_inodes .. "|" .. sb.root_ino for _, u in pairs(users) do lines[#lines + 1] = "U|" .. u.uid .. "|" .. u.name .. "|" .. u.gid .. "|" .. u.home .. "|" .. (u.hash or "-") end for i = 1, MAX_INODES do local ino = inodes[i] if ino then lines[#lines + 1] = "I|" .. ino.ino .. "|" .. ino.typ .. "|" .. ino.mode .. "|" .. ino.uid .. "|" .. ino.gid .. "|" .. ino.size .. "|" .. ino.mtime .. "|" if ino.typ == "d" and dirs[ino.ino] then local entries = {} for _, e in ipairs(dirs[ino.ino]) do entries[#entries + 1] = e.name .. ":" .. e.ino end lines[#lines + 1] = "D|" .. ino.ino .. "|" .. table.concat(entries, "|") elseif ino.typ == "f" and files[ino.ino] then lines[#lines + 1] = "F|" .. ino.ino .. "|" .. files[ino.ino] end end end return table.concat(lines, "\n") end local function fsDeserialize(str) inodes = {} dirs = {} files = {} users = {} sb = { seg_size = SEG_SIZE, max_inodes = MAX_INODES, root_ino = ROOT_INO } if not str or #str == 0 then return false end for line in str:gmatch("[^\n]+") do local typ = line:sub(1, 1) local rest = line:sub(3) if typ == "V" then elseif typ == "S" then local parts = split(rest, "|") if #parts >= 3 then sb.seg_size = tonumber(parts[1]) or SEG_SIZE sb.max_inodes = tonumber(parts[2]) or MAX_INODES sb.root_ino = tonumber(parts[3]) or ROOT_INO end elseif typ == "U" then local parts = split(rest, "|") if #parts >= 4 then users[tonumber(parts[1])] = { uid = tonumber(parts[1]), name = parts[2], gid = tonumber(parts[3]), home = parts[4], hash = parts[5] or "-" } end elseif typ == "I" then local parts = split(rest, "|") if #parts >= 7 then local ino = tonumber(parts[1]) inodes[ino] = { ino = ino, typ = parts[2], mode = parts[3], uid = tonumber(parts[4]), gid = tonumber(parts[5]), size = tonumber(parts[6]), mtime = tonumber(parts[7]) or 0 } end elseif typ == "D" then local parts = split(rest, "|") if #parts >= 2 then local ino = tonumber(parts[1]) local entries = {} for i = 2, #parts do local eparts = split(parts[i], ":") if #eparts >= 2 then entries[#entries + 1] = { name = eparts[1], ino = tonumber(eparts[2]) } end end dirs[ino] = entries end elseif typ == "F" then local parts = split(rest, "|") if #parts >= 2 then local ino = tonumber(parts[1]) local content = parts[2] or "" for i = 3, #parts do content = content .. "|" .. parts[i] end files[ino] = content end end end return next(inodes) ~= nil end local function findInodeByPath(path) if not path or #path == 0 then path = "/" end local parts = split(path, "/") local cur = inodes[sb.root_ino] if not cur then return nil end if path == "/" then return cur end local curIno = sb.root_ino for i = 1, #parts do local name = parts[i] if #name > 0 then local found = false local dentries = dirs[curIno] if dentries then for _, e in ipairs(dentries) do if e.name == name then curIno = e.ino cur = inodes[curIno] found = true break end end end if not found then return nil end end end return cur and inodes[curIno] or nil end local respSeq = 0 local function processReq(req) local op, rest = parseMsg(req) if not op then return "ERR|EINVAL" end respSeq = respSeq + 1 local seqStr = tostring(respSeq) .. "|" if op == "STAT" then local path = rest or "/" local ino = findInodeByPath(path) if not ino then return "ERR|" .. seqStr .. "ENOENT" end return "OK|" .. seqStr .. ino.ino .. "|" .. ino.typ .. "|" .. ino.mode .. "|" .. ino.uid .. "|" .. ino.gid .. "|" .. ino.size .. "|" .. ino.mtime elseif op == "READ" then local parts = split(rest or "", "|") local path = parts[1] or "" local offset = tonumber(parts[2]) or 0 local len = tonumber(parts[3]) or 8192 local ino = findInodeByPath(path) if not ino then return "ERR|" .. seqStr .. "ENOENT" end if ino.typ ~= "f" then return "ERR|" .. seqStr .. "EISDIR" end local content = files[ino.ino] or "" local data = content:sub(offset + 1, offset + len) return "OK|" .. seqStr .. data elseif op == "LS" then local path = rest or "/" local ino = findInodeByPath(path) if not ino then return "ERR|" .. seqStr .. "ENOENT" end if ino.typ ~= "d" then return "ERR|" .. seqStr .. "ENOTDIR" end local dentries = dirs[ino.ino] if not dentries then return "OK|" .. seqStr end local names = {} for _, e in ipairs(dentries) do names[#names + 1] = e.name end return "OK|" .. seqStr .. table.concat(names, "|") elseif op == "STATFS" then local total = sb.seg_size local used = #fsSerialize() return "OK|" .. seqStr .. total .. "|" .. used elseif op == "CHDIR" then local path = rest or "/" local ino = findInodeByPath(path) if not ino then return "ERR|" .. seqStr .. "ENOENT" end if ino.typ ~= "d" then return "ERR|" .. seqStr .. "ENOTDIR" end return "OK|" .. seqStr .. path elseif op == "WRITE" then return "ERR|" .. seqStr .. "EROFS" end return "ERR|" .. seqStr .. "EINVAL" end local function defaultFS() inodes = {} dirs = {} files = {} users = {} users[0] = { uid = 0, name = "root", gid = 0, home = "/", hash = "-" } users[100] = { uid = 100, name = "captain", gid = 10, home = "/home/captain", hash = "-" } local function mkIno(num, typ, mode, uid, gid, size) inodes[num] = { ino = num, typ = typ, mode = mode, uid = uid, gid = gid, size = size or 0, mtime = 0 } end local function mkDir(num, entries) dirs[num] = entries end local function mkFile(num, content) files[num] = content or "" local sz = #(content or "") if inodes[num] then inodes[num].size = sz end end mkIno(1, "d", "755", 0, 0, 0) mkDir(1, { { name = ".", ino = 1 }, { name = "..", ino = 1 }, { name = "home", ino = 2 }, { name = "etc", ino = 4 } }) mkIno(2, "d", "755", 0, 0, 0) mkDir(2, { { name = ".", ino = 2 }, { name = "..", ino = 1 }, { name = "captain", ino = 5 } }) mkIno(5, "d", "700", 100, 10, 0) mkDir(5, { { name = ".", ino = 5 }, { name = "..", ino = 2 }, { name = "readme.txt", ino = 3 } }) mkIno(3, "f", "644", 100, 10, 0) mkFile(3, "Welcome to AtlasOS v2.0!\n") mkIno(4, "d", "755", 0, 0, 0) 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 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 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 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 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 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() out[5] = str out[6] = 1 end local fsLoaded = false local prevReq = "" local writePending = false inp = {} function upd() if not fsLoaded then local memStr = inp[3] or "" if #memStr > 0 and fsDeserialize(memStr) then fsLoaded = true else fsLoaded = true defaultFS() writePending = true end end if writePending then local str = fsSerialize() out[5] = str out[6] = 1 writePending = false end if inp[1] ~= nil then local req = tostring(inp[1]) if #req == 0 then prevReq = "" elseif req ~= prevReq then prevReq = req local resp = processReq(req) out[1] = resp end else prevReq = "" end table.clear(inp) end