-- :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, "/") end function vfs.splitPath(path) local rv=string.split(path,"/") while table.indexOf(rv, "") ~= -1 do table.remove(rv, table.indexOf(rv, "")) end return rv end -- Resolve mount and disk path local function resolvePath(path) path = normalizePath(path) local mountPoint = nil local mountId = nil for id, mp in pairs(vfs.mounts) do if path == mp or (mp == "/" and path:sub(1, 1) == "/") or path:sub(1, #mp + 1) == mp .. "/" then if not mountPoint or #mp > #mountPoint then mountPoint = mp mountId = id end end end if not mountId then error("ENODEV") end local diskPath = path:sub(#mountPoint + 1) if diskPath == "" then diskPath = "/" end if kernel.config.logPathResolution then kernel.log("Path '"..path.."' resolved to disk '"..mountId.."' and path '"..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, type) return { handle = handle, mode = mode, path = path, meta = meta, type = type, refcount = 1 } end function vfs.newfd(fdobj) checkSystemLimit() total=total+1 local fd = allocFD(kernel.currentTask) kernel.currentTask.fd[fd]=fdobj 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 = 63, 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] if target == ".meta" then error("Cannot open metafile") end 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 local required = { "open", "type", "list", "attributes", "fileExists", "makeDirectory", "remove" } local function check(disk) for _, name in ipairs(required) do if type(disk[name]) ~= "function" then error("Invalid disk: missing method '" .. name .. "'") end end end function vfs.mount(target, diskOrId) if kernel.uid ~= 0 then error("EPERM") end if not target then error("EINVAL") end target = normalizeMountPoint(target) if not vfs.exists(target) then vfs.mkdir(target) end if vfs.type(target) ~= "directory" then error("EINVAL") end local disk local id if type(diskOrId) == "string" then disk = kernel.disks[diskOrId] if not disk then error("ENODEV") end check(disk) id = diskOrId elseif type(diskOrId) == "table" then check(disk) disk = diskOrId id = disk.address 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) 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 if disk:type(diskPath)~="directory" then handle = disk:open(diskPath, mode) if type(handle)~="table" then error("ENFILE") end end task.fd[fd] = newFileObj(handle, mode, path, meta, disk:type(diskPath)) if not disk.isvirt then total = total + 1 end 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 not file.handle.read 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 not file.handle.write 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 not file.handle.read then error("EBADF") end if not file.handle.seek 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 not file.handle.write then error("EBADF") end if not file.handle.seek 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 if not file.handle.seek 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 not file.handle.flush 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 task.fd[fd] = nil total = total - 1 file.refcount = file.refcount - 1 if file.refcount <= 0 then if file.handle.close then file.handle.close() end end 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 not inFile.handle.read then error("EBADF") end if not outFile.handle.write then error("EBADF") end local data = inFile.handle.read(count or 1024) if not data or data == "" then return end 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("ENOENT") end local meta = getFileMeta(path) checkperms(meta, "r") local list = disk:list(diskPath) if table.indexOf(list, ".meta") ~= -1 then table.remove(list, table.indexOf(list, ".meta")) end return list 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) if meta.owner ~= kernel.currentTask.uid then error("EACCES") end 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) if meta.owner ~= kernel.currentTask.uid then error("EACCES") end 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 function vfs.dup(oldfd) local task = kernel.currentTask local file = task.fd[oldfd] if not file then error("EBADF") end checkSystemLimit() local newfd = allocFD(task) file.refcount = file.refcount + 1 task.fd[newfd] = file total = total + 1 return newfd end function vfs.dup2(oldfd, newfd) local task = kernel.currentTask local file = task.fd[oldfd] if not file then error("EBADF") end if newfd < 0 or newfd >= kernel.config.maxFilesPerTask then error("EBADF") end if oldfd == newfd then return newfd end if task.fd[newfd] then vfs.close(newfd) end checkSystemLimit() file.refcount = file.refcount + 1 task.fd[newfd] = file total = total + 1 return newfd end function vfs.devctl(fd, method, ...) if not kernel.currentTask.fd[fd] then error("EBADF") end if not kernel.currentTask.fd[fd].handle[method] then error("EINVAL") end return kernel.currentTask.fd[fd].handle[method](...) 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 sys["getcwd"] = vfs.getcwd sys["chdir"] = vfs.chdir sys["dup"] = vfs.dup sys["dup2"] = vfs.dup2 sys["devctl"] = vfs.devctl kernel.log("VFS module loaded")