forked from Hyperion/HyperionOS
super dupper system update (it runs)
This commit is contained in:
@@ -1,351 +1,277 @@
|
||||
local args = {...}
|
||||
local kernel = args[1]
|
||||
local fs = {}
|
||||
|
||||
-- =====================================================================
|
||||
-- INTERNAL STATE
|
||||
-- =====================================================================
|
||||
|
||||
local disks = {} -- address → disk object
|
||||
local mounts = {["/"] = "$"} -- mountpoint → disk address (root = boot disk)
|
||||
local cwd = "/"
|
||||
|
||||
local SYMLINK_PREFIX = "#!@SYMLINK["
|
||||
local SYMLINK_SUFFIX = "]"
|
||||
local SYMLINK_MAX_DEPTH = 64
|
||||
|
||||
|
||||
-- =====================================================================
|
||||
-- PATH NORMALIZATION
|
||||
-- =====================================================================
|
||||
|
||||
local function splitPath(p)
|
||||
local t = {}
|
||||
for part in p:gmatch("[^/]+") do t[#t+1] = part end
|
||||
return t
|
||||
end
|
||||
local kernel = ...
|
||||
local vfs = {}
|
||||
vfs.mounts = { ["$"] = "/" }
|
||||
local disks = kernel.disks
|
||||
|
||||
-- Path handling
|
||||
local function normalizePath(path)
|
||||
if not path or path == "" then return "/" end
|
||||
|
||||
-- cwd
|
||||
if path:sub(1,1) ~= "/" then
|
||||
path=cwd..path
|
||||
end
|
||||
|
||||
|
||||
local parts = splitPath(path)
|
||||
local out = {}
|
||||
|
||||
for _,part in ipairs(parts) do
|
||||
local parts = {}
|
||||
for part in path:gmatch("[^/]+") do
|
||||
if part == ".." then
|
||||
if #out > 0 then table.remove(out) end
|
||||
if #parts > 0 then table.remove(parts) end
|
||||
elseif part ~= "." and part ~= "" then
|
||||
out[#out+1] = part
|
||||
table.insert(parts, part)
|
||||
end
|
||||
end
|
||||
|
||||
return "/" .. table.concat(out, "/")
|
||||
return "/" .. table.concat(parts, "/") .. (path:sub(-1) == "/" and "/" or "")
|
||||
end
|
||||
|
||||
local function resolvePath(path)
|
||||
local task = kernel.currentTask
|
||||
local cwd = task.cwd or "/"
|
||||
|
||||
-- =====================================================================
|
||||
-- DISK & MOUNT RESOLUTION
|
||||
-- =====================================================================
|
||||
if path:sub(1,1) ~= "/" then
|
||||
path = cwd .. path
|
||||
end
|
||||
|
||||
-- Finds which disk owns an absolute path
|
||||
local function resolveMount(abs)
|
||||
local best = "/"
|
||||
path = normalizePath(path)
|
||||
|
||||
for mount, addr in pairs(mounts) do
|
||||
if abs:sub(1, #mount) == mount then
|
||||
if #mount > #best then
|
||||
best = mount
|
||||
end
|
||||
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 disk = disks[mounts[best]]
|
||||
local diskPath = path:sub(#mountPoint + 1)
|
||||
return disks[mountId], diskPath
|
||||
end
|
||||
|
||||
-- File object creation
|
||||
local function newFileObject(disk, handle, mode, path)
|
||||
return {
|
||||
disk = disk,
|
||||
handle = handle,
|
||||
mode = mode,
|
||||
path = path,
|
||||
refcount = 1
|
||||
}
|
||||
end
|
||||
|
||||
local function allocFD(task)
|
||||
local fd = 0
|
||||
while task.fd[fd] do fd = fd + 1 end
|
||||
return fd
|
||||
end
|
||||
|
||||
local function allocFD(task)
|
||||
-- enforce per-task limit
|
||||
local count = 0
|
||||
for _ in pairs(task.fd) do count = count + 1 end
|
||||
if count >= kernel.config.maxFilesPerTask then
|
||||
error("EMFILE") -- Too many open files in this task
|
||||
end
|
||||
|
||||
-- find first free FD
|
||||
local fd = 0
|
||||
while task.fd[fd] do fd = fd + 1 end
|
||||
return fd
|
||||
end
|
||||
|
||||
local function checkSystemLimit()
|
||||
-- enforce system-wide limit
|
||||
local total = 0
|
||||
for _, task in pairs(kernel.tasks or {}) do
|
||||
for _ in pairs(task.fd) do total = total + 1 end
|
||||
end
|
||||
if total >= kernel.config.maxOpenFiles then
|
||||
error("ENFILE") -- Too many open files in the system
|
||||
end
|
||||
end
|
||||
|
||||
-- VFS syscalls
|
||||
function vfs.open(path, mode)
|
||||
local task = kernel.currentTask
|
||||
|
||||
-- check limits
|
||||
checkSystemLimit()
|
||||
|
||||
local disk, diskPath = resolvePath(path)
|
||||
if not disk then
|
||||
error("No disk registered for mount: " .. best)
|
||||
error("No disk mounted for path '"..path.."'")
|
||||
end
|
||||
|
||||
local sub = abs:sub(#best + 1)
|
||||
if sub == "" then sub = "/" end
|
||||
return disk, sub
|
||||
local handle = disk:open(diskPath, mode)
|
||||
if not handle then return nil end
|
||||
|
||||
local file = newFileObject(disk.address, handle, mode, path)
|
||||
local fd = allocFD(task)
|
||||
task.fd[fd] = file
|
||||
|
||||
return fd
|
||||
end
|
||||
|
||||
function vfs.close(fd)
|
||||
local task = kernel.currentTask
|
||||
local file = task.fd[fd]
|
||||
if not file then error("EBADF") end
|
||||
|
||||
|
||||
-- =====================================================================
|
||||
-- SYMLINK HANDLING
|
||||
-- =====================================================================
|
||||
|
||||
local function isSymlinkRaw(disk, p)
|
||||
if not disk:fileExists(p) then return false end
|
||||
local text = disk:readAllText(p)
|
||||
return text and text:sub(1, #SYMLINK_PREFIX) == SYMLINK_PREFIX
|
||||
end
|
||||
|
||||
local function readSymlinkRaw(disk, p)
|
||||
local text = disk:readAllText(p)
|
||||
if not text then return nil end
|
||||
if text:sub(1, #SYMLINK_PREFIX) ~= SYMLINK_PREFIX then return nil end
|
||||
|
||||
local t = text:sub(#SYMLINK_PREFIX + 1)
|
||||
if t:sub(-1) == SYMLINK_SUFFIX then
|
||||
t = t:sub(1, -2)
|
||||
task.fd[fd] = nil
|
||||
file.refcount = file.refcount - 1
|
||||
if file.refcount == 0 then
|
||||
file.handle.close()
|
||||
end
|
||||
return t
|
||||
return true
|
||||
end
|
||||
|
||||
function vfs.read(fd, count)
|
||||
local file = kernel.currentTask.fd[fd]
|
||||
if not file then error("EBADF") end
|
||||
if not file.mode:find("r") then error("File not open for reading") end
|
||||
return file.handle.read(count)
|
||||
end
|
||||
|
||||
-- =====================================================================
|
||||
-- FULL PATH RESOLUTION (FOLLOWS SYMLINKS)
|
||||
-- =====================================================================
|
||||
function vfs.write(fd, data)
|
||||
local file = kernel.currentTask.fd[fd]
|
||||
if not file then error("EBADF") end
|
||||
if not file.mode:find("w") then error("File not open for writing") end
|
||||
return file.handle.write(data)
|
||||
end
|
||||
|
||||
local function resolveSymlink(path)
|
||||
local abs = normalizePath(path)
|
||||
local parts = splitPath(abs)
|
||||
local out = {}
|
||||
function vfs.whereis(fd)
|
||||
local file = kernel.currentTask.fd[fd]
|
||||
if not file then error("EBADF") end
|
||||
return file.path
|
||||
end
|
||||
|
||||
local depth = 0
|
||||
local idx = 1
|
||||
-- Filesystem operations
|
||||
function vfs.mkdir(path)
|
||||
local disk, diskPath = resolvePath(path)
|
||||
if not disk then error("No disk mounted") end
|
||||
return disk:makeDirectory(diskPath)
|
||||
end
|
||||
|
||||
while idx <= #parts do
|
||||
local comp = parts[idx]
|
||||
function vfs.remove(path)
|
||||
local disk, diskPath = resolvePath(path)
|
||||
if not disk then error("No disk mounted") end
|
||||
return disk:remove(diskPath)
|
||||
end
|
||||
|
||||
if comp == "." then
|
||||
-- nothing
|
||||
elseif comp == ".." then
|
||||
if #out > 0 then table.remove(out) end
|
||||
else
|
||||
local curAbs = "/" .. table.concat(out, "/")
|
||||
if curAbs ~= "/" then curAbs = curAbs .. "/" end
|
||||
curAbs = curAbs .. comp
|
||||
|
||||
local disk, dpath = resolveMount(curAbs)
|
||||
|
||||
if isSymlinkRaw(disk, dpath) then
|
||||
depth = depth + 1
|
||||
if depth > SYMLINK_MAX_DEPTH then
|
||||
error("Too many symlink levels: " .. path)
|
||||
end
|
||||
|
||||
local target = readSymlinkRaw(disk, dpath)
|
||||
if target then
|
||||
local newAbs
|
||||
|
||||
if target:sub(1,1) == "/" then
|
||||
-- absolute target
|
||||
newAbs = normalizePath(target)
|
||||
else
|
||||
-- relative to current out[]
|
||||
local tmp = "/" .. table.concat(out, "/")
|
||||
if tmp ~= "/" then tmp = tmp .. "/" end
|
||||
tmp = tmp .. target
|
||||
newAbs = normalizePath(tmp)
|
||||
end
|
||||
|
||||
-- rebuild remaining parts
|
||||
local remaining = {}
|
||||
for j = idx + 1, #parts do
|
||||
remaining[#remaining+1] = parts[j]
|
||||
end
|
||||
|
||||
-- restart symlink resolution with new path
|
||||
abs = newAbs
|
||||
parts = splitPath(abs)
|
||||
|
||||
-- append remaining
|
||||
for _,x in ipairs(remaining) do parts[#parts+1] = x end
|
||||
|
||||
out = {}
|
||||
idx = 0
|
||||
else
|
||||
out[#out+1] = comp
|
||||
end
|
||||
else
|
||||
out[#out+1] = comp
|
||||
end
|
||||
end
|
||||
|
||||
idx = idx + 1
|
||||
function vfs.attributes(path)
|
||||
if type(path) == "number" then
|
||||
local file = kernel.currentTask.fd[path]
|
||||
if not file then error("EBADF") end
|
||||
return disks[file.disk]:attributes(file.path)
|
||||
end
|
||||
|
||||
local finalAbs = "/" .. table.concat(out, "/")
|
||||
return resolveMount(finalAbs)
|
||||
local disk, diskPath = resolvePath(path)
|
||||
if not disk then error("No disk mounted") end
|
||||
return disk:attributes(diskPath)
|
||||
end
|
||||
|
||||
function vfs.list(path)
|
||||
local disk, diskPath = resolvePath(path)
|
||||
if not disk then error("No disk mounted") end
|
||||
return disk:list(diskPath)
|
||||
end
|
||||
|
||||
function vfs.exists(path)
|
||||
local disk, diskPath = resolvePath(path)
|
||||
if not disk then return false end
|
||||
return disk:directoryExists(diskPath) or disk:fileExists(diskPath)
|
||||
end
|
||||
|
||||
-- =====================================================================
|
||||
-- PUBLIC API
|
||||
-- =====================================================================
|
||||
|
||||
-- MOUNT OPERATIONS -----------------------------------------------------
|
||||
|
||||
function fs.virtDisk(diskObj)
|
||||
if kernel.uid ~= 0 then error("Permission Denied") end
|
||||
if disks[diskObj.address] then
|
||||
error("Disk exists: " .. diskObj.address)
|
||||
function vfs.type(path)
|
||||
if type(path) == "number" then
|
||||
local file = kernel.currentTask.fd[path]
|
||||
if not file then error("EBADF") end
|
||||
return disks[file.disk]:type(file.path)
|
||||
end
|
||||
disks[diskObj.address] = diskObj
|
||||
local disk, diskPath = resolvePath(path)
|
||||
if not disk then error("No disk mounted") end
|
||||
return disk:type(diskPath)
|
||||
end
|
||||
|
||||
function fs.mount(disk, mountPoint)
|
||||
if kernel.uid ~= 0 then error("Permission Denied") end
|
||||
mountPoint = normalizePath(mountPoint)
|
||||
|
||||
local drive, path = resolveMount(normalizePath(mountPoint))
|
||||
if not drive:directoryExists(path) then error("Must mount on folder") end
|
||||
-- CWD
|
||||
function vfs.getcwd()
|
||||
return kernel.currentTask.cwd
|
||||
end
|
||||
|
||||
if mountPoint ~= "/" and mounts[mountPoint] then
|
||||
error("Already mounted: " .. mountPoint)
|
||||
function vfs.setcwd(path)
|
||||
if path:sub(-1) ~= "/" then path = path .. "/" end
|
||||
if path:sub(1,1) ~= "/" then path = "/" .. path end
|
||||
kernel.currentTask.cwd = normalizePath(path)
|
||||
end
|
||||
|
||||
-- Mounting
|
||||
function vfs.mount(diskId, path)
|
||||
if kernel.uid ~= 0 then error("Permission denied") end
|
||||
if not disks[diskId] then error("Unknown disk '"..diskId.."'") end
|
||||
if path:sub(-1) ~= "/" then path = path .. "/" end
|
||||
|
||||
for _,v in pairs(vfs.mounts) do
|
||||
if v == path then error("Mount point already used") end
|
||||
end
|
||||
|
||||
mounts[mountPoint] = disk
|
||||
vfs.mounts[diskId] = path
|
||||
end
|
||||
|
||||
function fs.unmount(mountPoint)
|
||||
if kernel.uid ~= 0 then error("Permission Denied") end
|
||||
mountPoint = normalizePath(mountPoint)
|
||||
|
||||
if mountPoint == "/" then error("Cannot unmount root") end
|
||||
mounts[mountPoint] = nil
|
||||
end
|
||||
|
||||
function fs.eject(addr)
|
||||
if kernel.uid ~= 0 then error("Permission Denied") end
|
||||
disks[addr] = nil
|
||||
end
|
||||
|
||||
function fs.isMount(path)
|
||||
if mounts[normalizePath(path)] then return true, mounts[normalizePath(path)] end
|
||||
end
|
||||
|
||||
-- SYMLINK API ----------------------------------------------------------
|
||||
|
||||
function fs.symlink(target, linkPath)
|
||||
kernel.log("WARNING: Symlinks are a untested feature if you find any bugs please report them to https://git.astronand.dev/Hyperion/HyperionOS","WARN")
|
||||
local disk, p = resolveMount(normalizePath(linkPath))
|
||||
return disk:writeAllText(p, SYMLINK_PREFIX .. target .. SYMLINK_SUFFIX)
|
||||
end
|
||||
|
||||
function fs.isLink(path)
|
||||
local disk, p = resolveMount(normalizePath(path))
|
||||
return isSymlinkRaw(disk, p)
|
||||
end
|
||||
|
||||
function fs.readLink(path)
|
||||
local disk, p = resolveMount(normalizePath(path))
|
||||
return readSymlinkRaw(disk, p)
|
||||
end
|
||||
|
||||
|
||||
-- FILE OPERATIONS ------------------------------------------------------
|
||||
|
||||
function fs.exists(path, ...)
|
||||
local disk, p = resolveSymlink(path)
|
||||
return disk:fileExists(p, ...) or disk:directoryExists(p, ...)
|
||||
end
|
||||
|
||||
function fs.isFile(path, ...)
|
||||
local disk, p = resolveSymlink(path)
|
||||
return disk:fileExists(p, ...)
|
||||
end
|
||||
|
||||
function fs.isDir(path, ...)
|
||||
local disk, p = resolveSymlink(path)
|
||||
return disk:directoryExists(p, ...)
|
||||
end
|
||||
|
||||
function fs.list(path, ...)
|
||||
local disk, p = resolveSymlink(path)
|
||||
return disk:list(p, ...)
|
||||
end
|
||||
|
||||
function fs.makeDir(path, ...)
|
||||
local disk, p = resolveSymlink(path)
|
||||
return disk:makeDirectory(p, ...)
|
||||
end
|
||||
|
||||
-- remove does NOT follow symlinks (UNIX semantics)
|
||||
function fs.remove(path, ...)
|
||||
if fs.isMount(path) then return "Cannot delete mounted folder" end
|
||||
local abs = normalizePath(path)
|
||||
local disk, p = resolveMount(abs)
|
||||
return disk:remove(p, ...)
|
||||
end
|
||||
|
||||
function fs.readAllText(path, ...)
|
||||
local disk, p = resolveSymlink(path)
|
||||
return disk:readAllText(p, ...)
|
||||
end
|
||||
|
||||
function fs.writeAllText(path, text, ...)
|
||||
local disk, p = resolveSymlink(path)
|
||||
return disk:writeAllText(p, text, ...)
|
||||
end
|
||||
|
||||
function fs.appendAllText(path, text, ...)
|
||||
local disk, p = resolveSymlink(path)
|
||||
return disk:appendAllText(p, text, ...)
|
||||
end
|
||||
|
||||
function fs.load(path, name, ...)
|
||||
return load(fs.readAllText(path, ...), name or path, nil, kernel._U)
|
||||
end
|
||||
|
||||
function fs.getSize(path, ...)
|
||||
local disk, p = resolveSymlink(path)
|
||||
return disk:getSize(p, ...)
|
||||
end
|
||||
|
||||
-- =====================================================================
|
||||
-- WD STUFF
|
||||
-- =====================================================================
|
||||
|
||||
function fs.setCwd(path)
|
||||
if not path or path == "" then return "/" end
|
||||
|
||||
-- ensure absolute
|
||||
if path:sub(1,1) ~= "/" then
|
||||
path = "/" .. path
|
||||
end
|
||||
|
||||
local parts = splitPath(path)
|
||||
local out = {}
|
||||
|
||||
for _,part in ipairs(parts) do
|
||||
if part == ".." then
|
||||
if #out > 0 then table.remove(out) end
|
||||
elseif part ~= "." and part ~= "" then
|
||||
out[#out+1] = part
|
||||
function vfs.unmount(path)
|
||||
if kernel.uid ~= 0 then error("Permission denied") end
|
||||
for k,v in pairs(vfs.mounts) do
|
||||
if v == path then
|
||||
vfs.mounts[k] = nil
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
cwd="/" .. table.concat(out, "/")
|
||||
return false
|
||||
end
|
||||
|
||||
function fs.getCwd(path)
|
||||
return cwd
|
||||
function vfs.getMounts()
|
||||
return vfs.mounts
|
||||
end
|
||||
|
||||
-- =====================================================================
|
||||
-- INIT
|
||||
-- =====================================================================
|
||||
function vfs.virtdisk(obj)
|
||||
kernel.disks[obj.address] = obj
|
||||
kernel.log("Registered virtual disk at "..obj.address)
|
||||
end
|
||||
|
||||
kernel.log("Loading disks for vfs")
|
||||
local ok,err = xpcall(function()
|
||||
for _,v in kernel.initdisks.list() do
|
||||
fs.virtDisk(v)
|
||||
-- Redirect file operations to VFS
|
||||
function vfs.dup(oldfd)
|
||||
local task = kernel.currentTask
|
||||
local file = task.fd[oldfd]
|
||||
if not file then error("EBADF") end
|
||||
|
||||
local newfd = allocFD(task)
|
||||
task.fd[newfd] = file
|
||||
file.refcount = file.refcount + 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 oldfd == newfd then return newfd end
|
||||
|
||||
if task.fd[newfd] then
|
||||
vfs.close(newfd)
|
||||
end
|
||||
end, debug.traceback)
|
||||
if not ok then kernel.panic(err) end
|
||||
|
||||
kernel.disks=disks
|
||||
kernel.mounts=mounts
|
||||
kernel.fs = fs
|
||||
kernel.cache.preload.fs = fs
|
||||
kernel.cache.preload.filesystem = kernel.cache.preload.fs
|
||||
task.fd[newfd] = file
|
||||
file.refcount = file.refcount + 1
|
||||
return newfd
|
||||
end
|
||||
|
||||
-- Syscall registration
|
||||
kernel.vfs = vfs
|
||||
kernel.syscalls["VFS_open"] = vfs.open
|
||||
kernel.syscalls["VFS_read"] = vfs.read
|
||||
kernel.syscalls["VFS_write"] = vfs.write
|
||||
kernel.syscalls["VFS_close"] = vfs.close
|
||||
kernel.syscalls["VFS_list"] = vfs.list
|
||||
kernel.syscalls["VFS_type"] = vfs.type
|
||||
kernel.syscalls["VFS_attributes"] = vfs.attributes
|
||||
kernel.syscalls["VFS_mkdir"] = vfs.mkdir
|
||||
kernel.syscalls["VFS_remove"] = vfs.remove
|
||||
kernel.syscalls["VFS_exists"] = vfs.exists
|
||||
kernel.syscalls["VFS_mount"] = vfs.mount
|
||||
kernel.syscalls["VFS_unmount"] = vfs.unmount
|
||||
kernel.syscalls["VFS_getcwd"] = vfs.getcwd
|
||||
kernel.syscalls["VFS_setcwd"] = vfs.setcwd
|
||||
kernel.syscalls["VFS_whereis"] = vfs.whereis
|
||||
kernel.syscalls["VFS_dup"] = vfs.dup
|
||||
kernel.syscalls["VFS_dup2"] = vfs.dup2
|
||||
|
||||
kernel.log("VFS module loaded")
|
||||
|
||||
Reference in New Issue
Block a user