--:Minify:-- local kernel = ... local vfs = {} kernel.vfs=vfs vfs.mounts = { ["$"] = "/" } vfs.disks = kernel.disks -- Path normalization local function normalizePath(path) local task = kernel.currentTask local cwd = task.cwd or "/" if path:sub(1,1) ~= "/" then path = cwd .. "/" .. path end local parts = {} for part in path:gmatch("[^/]+") do if part == ".." then if #parts > 0 then table.remove(parts) end elseif part ~= "." and part ~= "" then table.insert(parts, part) end end return "/" .. table.concat(parts, "/") .. (path:sub(-1) == "/" and "/" or "") end -- Resolve mount and disk path local function resolvePath(path) path = normalizePath(path) local mountPoint = "/" local mountId = "$" for k,v in pairs(vfs.mounts) do if path:sub(1,#v) == v and #v > #mountPoint then mountPoint = v mountId = k end end local diskPath = path:sub(#mountPoint) if diskPath == "" then diskPath = "/" end return vfs.disks[mountId], diskPath end -- Allocate file descriptor for current task local function allocFD(task) local fd = 0 while task.fd[fd] do fd = fd + 1 end if fd >= kernel.config.maxFilesPerTask then error("ENFILE") end return fd end -- System-wide open file limit local total = 0 local function checkSystemLimit() if total >= kernel.config.maxOpenFiles - 16 then error("ENFILE") end end -- File object constructor local function newFileObj(handle, mode, path, meta) return { handle = handle, mode = mode, path = path, meta = meta } end -- Validate mode local function ismode(mode) if not (mode == "r" or mode == "w" or mode == "a") then error("EINVAL") end end -- Parse metafile local function parseMetafile(file) if not file or file == "" then return {} end local ret = {} local pointer = 1 while pointer <= #file do local namelen = file:byte(pointer) pointer = pointer + 1 local name = file:sub(pointer, pointer + namelen - 1) pointer = pointer + namelen local owner = file:byte(pointer) local group = file:byte(pointer + 1) local perms = file:byte(pointer + 2) pointer = pointer + 3 local cmetalen = file:byte(pointer) pointer = pointer + 1 local cmeta = "" if cmetalen > 0 then cmeta = file:sub(pointer, pointer + cmetalen - 1) pointer = pointer + cmetalen end ret[name] = { owner = owner, group = group, perms = perms, cmeta = cmeta } end return ret end -- Build metafile local function makeMetafile(meta) local file="" for name, m in pairs(meta) do local entry = "" entry = entry .. string.char(#name) .. name entry = entry .. string.char(m.owner, m.group, m.perms) entry = entry .. string.char(#m.cmeta) .. m.cmeta file = file .. entry end return file end -- Get file metadata object local function getFileMeta(path) local disk, fullPath = resolvePath(path) fullPath = normalizePath(fullPath) local parts = {} for p in fullPath:gmatch("[^/]+") do table.insert(parts, p) end -- default fallback local default = { owner=0, group=0, perms=62, cmeta="" } -- walk from deepest parent upward for i = #parts, 1, -1 do local parent = "/" .. table.concat(parts, "/", 1, i-1) if parent ~= "/" then parent = parent .. "/" end local target = parts[i] local metaPath = parent .. ".meta" if disk:fileExists(metaPath) then local f = disk:open(metaPath, "r") local text = f.read(65535) f.close() local parsed = parseMetafile(text) if parsed[target] then return parsed[target] end end end return default end local function ensureParentMeta(path) local disk, fullPath = resolvePath(path) fullPath = normalizePath(fullPath) -- split parent + name local parent, name = fullPath:match("^(.*)/([^/]+)$") if not parent then parent = "/" name = fullPath:gsub("^/", "") end if name == ".meta" then error("Cannot open metafile") end if parent ~= "/" and parent:sub(-1) ~= "/" then parent = parent .. "/" end local metaPath = parent .. ".meta" if not disk:fileExists(metaPath) then local f = disk:open(metaPath, "w") f.write("") f.close() end return metaPath, name end -- Permission checking local function checkperms(meta, mode) local modes = { r = {owner=5, group=3, everyone=1}, w = {owner=4, group=2, everyone=0}, a = {owner=4, group=2, everyone=0} } local bits = meta.perms local function bit_is_set(num, bit) return math.floor(num / (2^bit)) % 2 == 1 end if kernel.uid == 0 then return true end if kernel.uid == meta.owner and bit_is_set(bits, modes[mode].owner) then return true end if meta.group and kernel.groups then for _, gid in ipairs(kernel.groups) do if gid == meta.group and bit_is_set(bits, modes[mode].group) then return true end end end if bit_is_set(bits, modes[mode].everyone) then return true end error("EACCES") end -- mounts local function normalizeMountPoint(path) path = normalizePath(path) if path ~= "/" and path:sub(-1) == "/" then path = path:sub(1, -2) end return path end function vfs.mount(target, diskOrId) if kernel.uid ~= 0 then error("EPERM") end if not target then error("EINVAL") end target = normalizeMountPoint(target) local disk local id if type(diskOrId) == "string" then disk = kernel.disks[diskOrId] id = diskOrId if not disk then error("ENODEV") end elseif type(diskOrId) == "table" then disk = diskOrId id = tostring(disk) vfs.disks[id] = disk else error("EINVAL") end -- Prevent shadowing an existing mount for _, mp in pairs(vfs.mounts) do if mp == target then error("EBUSY") end end vfs.mounts[id] = target return true end function vfs.umount(target) if kernel.uid ~= 0 then error("EPERM") end if not target then error("EINVAL") end target = normalizeMountPoint(target) for id, mp in pairs(vfs.mounts) do if mp == target then if id == "$" then error("EBUSY") end -- root fs vfs.mounts[id] = nil return true end end error("EINVAL") end -- Open file function vfs.open(path, mode) ismode(mode) checkSystemLimit() local task = kernel.currentTask local fd = allocFD(task) local disk, diskPath = resolvePath(path) if not disk then error("NODISK") end local meta = getFileMeta(path) checkperms(meta, mode) local handle = disk:open(diskPath, mode) task.fd[fd] = newFileObj(handle, mode, path, meta) total = total + 1 return fd end -- Read function vfs.read(fd, count) local task = kernel.currentTask local file = task.fd[fd] if not file then error("EBADF") end if file.mode ~= "r" then error("EBADF") end return file.handle.read(count or 1) end -- Write function vfs.write(fd, content) local task = kernel.currentTask local file = task.fd[fd] if not file then error("EBADF") end if file.mode ~= "w" and file.mode ~= "a" then error("EBADF") end return file.handle.write(content) end -- Pread / Pwrite function vfs.pread(fd, count, offset) local task = kernel.currentTask local file = task.fd[fd] if not file then error("EBADF") end if file.mode ~= "r" then error("EBADF") end file.handle.seek("set", offset) return file.handle.read(count or 1) end function vfs.pwrite(fd, content, offset) local task = kernel.currentTask local file = task.fd[fd] if not file then error("EBADF") end if file.mode ~= "w" and file.mode ~= "a" then error("EBADF") end file.handle.seek("set", offset) return file.handle.write(content) end -- Seek function vfs.lseek(fd, offset, whence) local task = kernel.currentTask local file = task.fd[fd] if not file then error("EBADF") end return file.handle.seek(whence or "set", offset) end -- Fsync function vfs.fsync(fd) local task = kernel.currentTask local file = task.fd[fd] if not file then error("EBADF") end if file.mode ~= "w" and file.mode ~= "a" then error("EBADF") end file.handle.flush() end -- Close function vfs.close(fd) local task = kernel.currentTask local file = task.fd[fd] if not file then error("EBADF") end file.handle.close() task.fd[fd] = nil total = total - 1 end -- Sendfile function vfs.sendfile(outfd, infd, count) local task = kernel.currentTask local inFile = task.fd[infd] local outFile = task.fd[outfd] if not inFile or not outFile then error("EBADF") end if inFile.mode ~= "r" then error("EBADF") end if outFile.mode ~= "w" and outFile.mode ~= "a" then error("EBADF") end local data = inFile.handle.read(count or 1024) return outFile.handle.write(data) end -- Stat / Fstat function vfs.stat(path) local disk, diskPath = resolvePath(path) local meta = getFileMeta(path) local attrs = disk:attributes(diskPath) return { size = attrs.size, modified = attrs.modified, created = attrs.created, owner = meta.owner, group = meta.group, xattr = meta.cmeta } end function vfs.fstat(fd) local task = kernel.currentTask local file = task.fd[fd] if not file then error("EBADF") end local disk, path = resolvePath(file.path) local attrs = disk:attributes(path) return { size = attrs.size, modified = attrs.modified, created = attrs.created, owner = file.meta.owner, group = file.meta.group, xattr = file.meta.cmeta } end -- Directory operations function vfs.listdir(path) local disk, diskPath = resolvePath(path) if disk:type(diskPath) ~= "directory" then error("ENOTDIR") end local meta = getFileMeta(path) checkperms(meta, "r") return disk:list(diskPath) end function vfs.mkdir(path) local disk, diskPath = resolvePath(path) local meta = getFileMeta(path) checkperms(meta, "w") disk:makeDirectory(diskPath) end function vfs.remove(path) local disk, diskPath = resolvePath(path) local meta = getFileMeta(path) checkperms(meta, "w") disk:remove(diskPath) end -- Permission functions function vfs.chmod(path, perms) local disk, diskPath = resolvePath(path) local meta = getFileMeta(path) checkperms(meta, "w") meta.perms = perms local mpath, target = ensureParentMeta(path) local mf = disk:open(mpath, "r") local text = mf.read(65535) mf.close() local parsed = parseMetafile(text) parsed[target] = meta local f = disk:open(mpath, "w") f.write(makeMetafile(parsed)) f.close() end function vfs.fchmod(fd, perms) local task = kernel.currentTask local file = task.fd[fd] if not file then error("EBADF") end vfs.chmod(file.path, perms) end function vfs.chown(path, uid, gid) local disk, diskPath = resolvePath(path) local meta = getFileMeta(path) checkperms(meta, "w") meta.owner = uid meta.group = gid local mpath, target = ensureParentMeta(path) local mf = disk:open(mpath, "r") local text = mf.read(65535) mf.close() local parsed = parseMetafile(text) parsed[target] = meta local f = disk:open(mpath, "w") f.write(makeMetafile(parsed)) f.close() end function vfs.fchown(fd, uid, gid) local task = kernel.currentTask local file = task.fd[fd] if not file then error("EBADF") end vfs.chown(file.path, uid, gid) end function vfs.exists(path) local disk, diskPath = resolvePath(path) local meta = getFileMeta(path) checkperms(meta, "r") return disk:fileExists(diskPath) end function vfs.type(path) local disk, diskPath = resolvePath(path) local meta = getFileMeta(path) checkperms(meta, "r") return disk:type(diskPath) end function vfs.getcwd() return kernel.currentTask.cwd end function vfs.chdir(path) kernel.currentTask.cwd=path end -- Export syscalls local sys = kernel.syscalls sys["open"] = vfs.open sys["close"] = vfs.close sys["read"] = vfs.read sys["write"] = vfs.write sys["pread"] = vfs.pread sys["pwrite"] = vfs.pwrite sys["lseek"] = vfs.lseek sys["fsync"] = vfs.fsync sys["sendfile"] = vfs.sendfile sys["stat"] = vfs.stat sys["fstat"] = vfs.fstat sys["mkdir"] = vfs.mkdir sys["remove"] = vfs.remove sys["listdir"] = vfs.listdir sys["chmod"] = vfs.chmod sys["fchmod"] = vfs.fchmod sys["chown"] = vfs.chown sys["fchown"] = vfs.fchown sys["exists"] = vfs.exists sys["type"] = vfs.type sys["mount"] = vfs.mount sys["umount"] = vfs.umount kernel.log("VFS module loaded")