neonucleus/data/OpenOS/lib/core/full_filesystem.lua
IonutParau 687cfebd00 testing version of LuaBIOS and OpenOS
people were having issues getting them to work so now we promote consistency
2025-06-28 20:41:49 +02:00

364 lines
9.9 KiB
Lua

local filesystem = require("filesystem")
local component = require("component")
local shell = require("shell")
function filesystem.makeDirectory(path)
if filesystem.exists(path) then
return nil, "file or directory with that name already exists"
end
local node, rest = filesystem.findNode(path)
if node.fs and rest then
local success, reason = node.fs.makeDirectory(rest)
if not success and not reason and node.fs.isReadOnly() then
reason = "filesystem is readonly"
end
return success, reason
end
if node.fs then
return nil, "virtual directory with that name already exists"
end
return nil, "cannot create a directory in a virtual directory"
end
function filesystem.lastModified(path)
local node, rest, vnode, vrest = filesystem.findNode(path, false, true)
if not node or not vnode.fs and not vrest then
return 0 -- virtual directory
end
if node.fs and rest then
return node.fs.lastModified(rest)
end
return 0 -- no such file or directory
end
function filesystem.mounts()
local tmp = {}
for path,node in pairs(filesystem.fstab) do
table.insert(tmp, {node.fs,path})
end
return function()
local next = table.remove(tmp)
if next then return table.unpack(next) end
end
end
function filesystem.link(target, linkpath)
checkArg(1, target, "string")
checkArg(2, linkpath, "string")
if filesystem.exists(linkpath) then
return nil, "file already exists"
end
local linkpath_parent = filesystem.path(linkpath)
if not filesystem.exists(linkpath_parent) then
return nil, "no such directory"
end
local linkpath_real, reason = filesystem.realPath(linkpath_parent)
if not linkpath_real then
return nil, reason
end
if not filesystem.isDirectory(linkpath_real) then
return nil, "not a directory"
end
local _, _, vnode, _ = filesystem.findNode(linkpath_real, true)
vnode.links[filesystem.name(linkpath)] = target
return true
end
function filesystem.umount(fsOrPath)
checkArg(1, fsOrPath, "string", "table")
local real
local fs
local addr
if type(fsOrPath) == "string" then
real = filesystem.realPath(fsOrPath)
addr = fsOrPath
else -- table
fs = fsOrPath
end
local paths = {}
for path,node in pairs(filesystem.fstab) do
if real == path or addr == node.fs.address or fs == node.fs then
table.insert(paths, path)
end
end
for _,path in ipairs(paths) do
local node = filesystem.fstab[path]
filesystem.fstab[path] = nil
node.fs = nil
node.parent.children[node.name] = nil
end
return #paths > 0
end
function filesystem.size(path)
local node, rest, vnode, vrest = filesystem.findNode(path, false, true)
if not node or not vnode.fs and (not vrest or vnode.links[vrest]) then
return 0 -- virtual directory or symlink
end
if node.fs and rest then
return node.fs.size(rest)
end
return 0 -- no such file or directory
end
function filesystem.isLink(path)
local name = filesystem.name(path)
local node, rest, vnode, vrest = filesystem.findNode(filesystem.path(path), false, true)
if not node then return nil, rest end
local target = vnode.links[name]
-- having vrest here indicates we are not at the
-- owning vnode due to a mount point above this point
-- but we can have a target when there is a link at
-- the mount point root, with the same name
if not vrest and target ~= nil then
return true, target
end
return false
end
function filesystem.copy(fromPath, toPath)
local data = false
local input, reason = filesystem.open(fromPath, "rb")
if input then
local output = filesystem.open(toPath, "wb")
if output then
repeat
data, reason = input:read(1024)
if not data then break end
data, reason = output:write(data)
if not data then data, reason = false, "failed to write" end
until not data
output:close()
end
input:close()
end
return data == nil, reason
end
local function readonly_wrap(proxy)
checkArg(1, proxy, "table")
if proxy.isReadOnly() then
return proxy
end
local function roerr() return nil, "filesystem is readonly" end
return setmetatable({
rename = roerr,
open = function(path, mode)
checkArg(1, path, "string")
checkArg(2, mode, "string")
if mode:match("[wa]") then
return roerr()
end
return proxy.open(path, mode)
end,
isReadOnly = function()
return true
end,
write = roerr,
setLabel = roerr,
makeDirectory = roerr,
remove = roerr,
}, {__index=proxy})
end
local function bind_proxy(path)
local real, reason = filesystem.realPath(path)
if not real then
return nil, reason
end
if not filesystem.isDirectory(real) then
return nil, "must bind to a directory"
end
local real_fs, real_fs_path = filesystem.get(real)
if real == real_fs_path then
return real_fs
end
-- turn /tmp/foo into foo
local rest = real:sub(#real_fs_path + 1)
local function wrap_relative(fp)
return function(mpath, ...)
return fp(filesystem.concat(rest, mpath), ...)
end
end
local bind = {
type = "filesystem_bind",
address = real,
isReadOnly = real_fs.isReadOnly,
list = wrap_relative(real_fs.list),
isDirectory = wrap_relative(real_fs.isDirectory),
size = wrap_relative(real_fs.size),
lastModified = wrap_relative(real_fs.lastModified),
exists = wrap_relative(real_fs.exists),
open = wrap_relative(real_fs.open),
remove = wrap_relative(real_fs.remove),
read = real_fs.read,
write = real_fs.write,
close = real_fs.close,
getLabel = function() return "" end,
setLabel = function() return nil, "cannot set the label of a bind point" end,
}
return bind
end
filesystem.internal = {}
function filesystem.internal.proxy(filter, options)
checkArg(1, filter, "string")
checkArg(2, options, "table", "nil")
options = options or {}
local address, proxy, reason
if options.bind then
proxy, reason = bind_proxy(filter)
else
-- no options: filter should be a label or partial address
for c in component.list("filesystem", true) do
if component.invoke(c, "getLabel") == filter then
address = c
break
end
if c:sub(1, filter:len()) == filter then
address = c
break
end
end
if not address then
return nil, "no such file system"
end
proxy, reason = component.proxy(address)
end
if not proxy then
return proxy, reason
end
if options.readonly then
proxy = readonly_wrap(proxy)
end
return proxy
end
function filesystem.remove(path)
local function removeVirtual()
local _, _, vnode, vrest = filesystem.findNode(filesystem.path(path), false, true)
-- vrest represents the remaining path beyond vnode
-- vrest is nil if vnode reaches the full path
-- thus, if vrest is NOT NIL, then we SHOULD NOT remove children nor links
if not vrest then
local name = filesystem.name(path)
if vnode.children[name] or vnode.links[name] then
vnode.children[name] = nil
vnode.links[name] = nil
while vnode and vnode.parent and not vnode.fs and not next(vnode.children) and not next(vnode.links) do
vnode.parent.children[vnode.name] = nil
vnode = vnode.parent
end
return true
end
end
-- return false even if vrest is nil because this means it was a expected
-- to be a real file
return false
end
local function removePhysical()
local node, rest = filesystem.findNode(path)
if node.fs and rest then
return node.fs.remove(rest)
end
return false
end
local success = removeVirtual()
success = removePhysical() or success -- Always run.
if success then return true
else return nil, "no such file or directory"
end
end
function filesystem.rename(oldPath, newPath)
if filesystem.isLink(oldPath) then
local _, _, vnode, _ = filesystem.findNode(filesystem.path(oldPath))
local target = vnode.links[filesystem.name(oldPath)]
local result, reason = filesystem.link(target, newPath)
if result then
filesystem.remove(oldPath)
end
return result, reason
else
local oldNode, oldRest = filesystem.findNode(oldPath)
local newNode, newRest = filesystem.findNode(newPath)
if oldNode.fs and oldRest and newNode.fs and newRest then
if oldNode.fs.address == newNode.fs.address then
return oldNode.fs.rename(oldRest, newRest)
else
local result, reason = filesystem.copy(oldPath, newPath)
if result then
return filesystem.remove(oldPath)
else
return nil, reason
end
end
end
return nil, "trying to read from or write to virtual directory"
end
end
local isAutorunEnabled = nil
local function saveConfig()
local root = filesystem.get("/")
if root and not root.isReadOnly() then
local f = filesystem.open("/etc/filesystem.cfg", "w")
if f then
f:write("autorun="..tostring(isAutorunEnabled))
f:close()
end
end
end
function filesystem.isAutorunEnabled()
if isAutorunEnabled == nil then
local env = {}
local config = loadfile("/etc/filesystem.cfg", nil, env)
if config then
pcall(config)
isAutorunEnabled = not not env.autorun
else
isAutorunEnabled = true
end
saveConfig()
end
return isAutorunEnabled
end
function filesystem.setAutorunEnabled(value)
checkArg(1, value, "boolean")
isAutorunEnabled = value
saveConfig()
end
-- luacheck: globals os
os.remove = filesystem.remove
os.rename = filesystem.rename
os.execute = function(command)
if not command then
return type(shell) == "table"
end
return shell.execute(command)
end
function os.exit(code)
error({reason="terminated", code=code}, 0)
end
function os.tmpname()
local path = os.getenv("TMPDIR") or "/tmp"
if filesystem.exists(path) then
for _ = 1, 10 do
local name = filesystem.concat(path, tostring(math.random(1, 0x7FFFFFFF)))
if not filesystem.exists(name) then
return name
end
end
end
end