-- :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) or "" 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) or "" 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")