Files
Halyde/halyde/lib/filesystem.lua
T
Ponali fe6cd4cd72 v2.7.1 - Fixed a bug where drivers wouldn't load, and listing /special/drive would return an error when a Computers tape drive is connected
The startupapps.json config file didn't have drvload.lua when recently installing Halyde. component.list("drive") will not just list components with the 'drive' type, but also with types that contain 'drive'. this includes 'tape_drive' (Computronics), so when calling getCapacity, the filesystem library errors out.
2025-07-23 16:31:58 +02:00

507 lines
16 KiB
Lua

local loadfile = ... -- raw loadfile from boot.lua
local component, computer
if loadfile then
unicode = loadfile("/halyde/lib/unicode.lua")(loadfile)
component = loadfile("/halyde/lib/component.lua")(loadfile)
computer = _G.computer
elseif import then
unicode = import("unicode")
component = import("component")
computer = import("computer")
end
local filesystem = {}
function filesystem.canonical(path)
checkArg(1, path, "string")
local segList = {}
if path:sub(1, 1) ~= "/" then
path = "/" .. path
end
path = path:gsub("/+", "/")
for segment in path:gmatch("[^/]+") do
if segment == ".." and segList[1] then
table.remove(segList, #segList)
elseif segment ~= "." then
table.insert(segList, segment)
end
end
return "/" .. table.concat(segList, "/")
end
function filesystem.concat(path1, path2)
checkArg(1, path1, "string")
checkArg(2, path2, "string")
if path1:sub(-1, -1) == "/" then
path1 = path1:sub(1, -2)
end
if path2:sub(1, 1) ~= "/" then
path2 = "/" .. path2
end
return path1 .. path2
end
function filesystem.absolutePath(path) -- returns the address and absolute path of an object
checkArg(1, path, "string")
path = filesystem.canonical(path)
local address = nil
if path:find("^/tmp") then
address = computer.tmpAddress()
path = path:sub(5)
elseif path:find("^/mnt/...") then
address = component.get(path:sub(6,8))
if not address then
address = computer.getBootAddress()
else
path = path:sub(9)
end
else
address = computer.getBootAddress()
end
if not address then
return nil, "no such component"
end
return address, path
end
function filesystem.exists(path) -- check if path exists
checkArg(1, path, "string")
local address, absPath = filesystem.absolutePath(path)
if not address then
return false
end
if absPath:find("^/special/drive/...") then
return not not (computer.getBootAddress() and component.get(absPath:sub(16,18)))
end
if absPath:find("^/special/eeprom/") then
return table.find({"init.lua","data.bin","label.txt"},absPath:sub(17))
end
return component.invoke(address, "exists", absPath)
end
local function readBytes(self,n)
n = n or 1
if n==1 then
local byte = self:read(1)
if byte==nil then return nil end
return string.byte(byte)
end
local bytes, res = {string.byte(self:read(n),1,n)}, 0
if self.littleEndian then
for i=#bytes,1,-1 do
res = (res<<8)&0xFFFFFFFF | bytes[i]
end
else
for i=1,#bytes do
res = (res<<8)&0xFFFFFFFF | bytes[i]
end
end
return res
end
local function readUnicodeChar(self)
return unicode.readChar(function()
return self:readBytes(1)
end)
end
local function iterateBytes(self)
return function()
local byte = readBytes(self,1)
if byte==nil then self:close() end
return byte
end
end
local function iterateUnicodeChars(self)
return unicode.iterate(iterateBytes(self))
end
function filesystem.makeReadStream(content)
local properHandle = {}
local readcursor = 1
function properHandle.read(self, amount)
checkArg(2, amount, "number")
local limit = string.len(content)+1
local out = nil
if readcursor<limit then
if amount==math.huge then
out = string.sub(content,math.min(readcursor,limit))
else
out = string.sub(content,math.min(readcursor,limit),math.min(readcursor+amount-1,limit))
end
end
readcursor=readcursor+amount
if out=="" then
return nil
end
return out
end
properHandle.readBytes = readBytes
properHandle.readUnicodeChar = readUnicodeChar
properHandle.iterateBytes = iterateBytes
properHandle.iterateUnicodeChars = iterateUnicodeChars
function properHandle.write()
return nil
end
function properHandle.close()
content=nil
end
return properHandle
end
function filesystem.open(path, mode, buffered) -- opens a file and returns its handle
checkArg(1, path, "string")
checkArg(2, mode, "string", "nil")
checkArg(3, buffered, "boolean", "nil")
if not mode then
mode = "r"
end
if buffered == nil then
buffered = true
end
if not (mode == "r" or mode == "w" or mode == "rb" or mode == "wb" or mode == "a" or mode == "ab") then
return nil, "invalid handle type"
end
if path:find("^/special") and not filesystem.exists(path) then
return nil, "/special does not allow creating files"
end
local address, absPath = filesystem.absolutePath(path)
local unmanagedDrive = address==computer.getBootAddress() and absPath:find("^/special/drive")
local unmanagedProxy, sectorSize, sectorCount, handle
if unmanagedDrive then
unmanagedProxy = component.proxy(component.get(absPath:sub(16,18)))
sectorSize = unmanagedProxy.getSectorSize()
sectorCount = math.ceil(unmanagedProxy.getCapacity()/sectorSize)
elseif not (address==computer.getBootAddress() and absPath:find("^/special/")) then
local handleArgs = {component.invoke(address, "open", absPath, mode)}
handle = handleArgs[1]
if not handle then
return table.unpack(handleArgs)
end
handleArgs = nil
end
local properHandle = {}
properHandle.handle = handle
properHandle.address = address
local content = nil
local readcursor = 1
if buffered and mode:sub(1,1)=="r" then
content=""
repeat
tmpdata = component.invoke(address, "read", handle, math.huge or math.maxinteger)
content = content .. (tmpdata or "")
until not tmpdata
component.invoke(address, "close", handle)
end
function properHandle.read(self, amount)
checkArg(2, amount, "number")
if unmanagedDrive then
local sectorIdx = ((readcursor-1)//sectorSize)+1
if sectorIdx>sectorCount then return nil end
local sector = unmanagedProxy.readSector(sectorIdx)
local data = sector:sub(((readcursor-1)%sectorSize)+1,((readcursor+math.min(amount,sectorSize)-2)%sectorSize)+1)
readcursor=readcursor+#data
if data=="" then return nil end
return data
else
if buffered then
local limit = string.len(content)+1
local out = nil
if readcursor<limit then
if amount==math.huge then
out = string.sub(content,math.min(readcursor,limit))
else
out = string.sub(content,math.min(readcursor,limit),math.min(readcursor+amount-1,limit))
end
end
readcursor=readcursor+amount
if out=="" then
return nil
end
return out
else
return component.invoke(self.address, "read", self.handle, amount)
end
end
end
properHandle.readBytes = readBytes
properHandle.readUnicodeChar = readUnicodeChar
properHandle.iterateBytes = iterateBytes
properHandle.iterateUnicodeChars = iterateUnicodeChars
function properHandle.write(self, data)
checkArg(2, data, "string")
if unmanagedDrive then
local startSector = ((readcursor-1)//sectorSize)+1
if startSector>sectorCount then return nil, "not enough space" end
local startSByte = ((readcursor-1)%sectorSize)+1
local sect = unmanagedProxy.readSector(startSector)
unmanagedProxy.writeSector(startSector,sect:sub(1,startSByte-1)..data:sub(1,sectorSize-startSByte+1))
for i=2,(#data+startSByte)//sectorSize do
if startSector+i-1>sectorCount then return nil, "not enough space" end
unmanagedProxy.writeSector(startSector+i-1,data:sub(startSByte+sectorSize*(i-1),startSByte+sectorSize*i-1))
end
readcursor=readcursor+#data
return true
else
return component.invoke(self.address, "write", self.handle, data)
end
end
function properHandle.close(self)
if buffered then
content = nil
else
return component.invoke(self.address, "close", self.handle)
end
end
if address==computer.getBootAddress() then
local eeprom
pcall(function()
eeprom = component.eeprom
end)
if eeprom then
local getFunc, setFunc
if absPath=="/special/eeprom/init.lua" then
getFunc,setFunc = "get","set"
elseif absPath=="/special/eeprom/data.bin" then
getFunc,setFunc = "getData","setData"
elseif absPath=="/special/eeprom/label.txt" then
getFunc,setFunc = "getLabel","setLabel"
end
if mode:sub(1,1)=="r" and getFunc then
local stream = filesystem.makeReadStream(eeprom[getFunc]() or "")
properHandle.read = stream.read
properHandle.close = stream.close
elseif mode:sub(1,1)=="w" and setFunc then
local content = ""
function properHandle.write(self, data)
checkArg(2, data, "string")
content=content..data
end
function properHandle.close(self)
return eeprom[setFunc](content)
end
end
end
end
return properHandle
end
function filesystem.list(path)
checkArg(1, path, "string")
path = filesystem.canonical(path)
if path == "/mnt" then
-- list drives
local returnTable = {}
local tmpAddress = computer.tmpAddress()
for address, _ in component.list("filesystem") do
if address~=tmpAddress then
table.insert(returnTable, address:sub(1, 3) .. "/")
end
end
return returnTable
elseif path == "/special/drive" then
local returnTable = {}
local tmpAddress = computer.tmpAddress()
for address, type in component.list("drive") do
if address~=tmpAddress and type=="drive" then
table.insert(returnTable, address:sub(1, 3))
end
end
return returnTable
elseif path=="/special/eeprom" then
return {"init.lua","data.bin","label.txt"}
else
local address, absPath = filesystem.absolutePath(path)
if not address then
return false
end
return component.invoke(address, "list", absPath)
end
end
function filesystem.size(path)
checkArg(1, path, "string")
local address, absPath = filesystem.absolutePath(path)
if not address then
return false
end
if address==computer.getBootAddress() then
if absPath:find("^/special/drive") then
local drive = component.get(absPath:sub(16,18))
if not drive then return false end
return component.invoke(drive,"getCapacity")
elseif absPath:find("^/special/eeprom") then
local eeprom
pcall(function()
eeprom = component.eeprom
end)
if eeprom then
local getFunc
if absPath=="/special/eeprom/init.lua" then
getFunc = "get"
elseif absPath=="/special/eeprom/data.bin" then
getFunc = "getData"
elseif absPath=="/special/eeprom/label.txt" then
getFunc = "getLabel"
end
return #(eeprom[getFunc]())
end
end
end
return component.invoke(address, "size", absPath)
end
local function getRecursiveList(address,absPath)
local list = component.invoke(address,"list",absPath)
local dirList = {}
local listChanged = true
while listChanged do
listChanged = false
for i=1,#list do
if component.invoke(address, "isDirectory", absPath.."/"..list[i]) then
listChanged = true
local dir = list[i]
if dir:sub(-1)=="/" then dir=dir:sub(1,-2) end
table.insert(dirList,dir)
table.remove(list,i)
local subDir = component.invoke(address,"list",absPath.."/"..dir)
for j=1,#subDir do table.insert(list,dir.."/"..subDir[j]) end
end
end
end
return list,dirList
end
local function copyContent(fromHandle,toHandle)
if not (fromHandle and toHandle) then return end
local memory = math.floor(computer.freeMemory()*0.8)
local tmpdata
while true do
tmpdata = fromHandle:read(memory)
if not tmpdata then break end
local status,reason = toHandle:write(tmpdata)
if status~=true then break end
end
fromHandle:close()
toHandle:close()
end
local function copyRecursive(fromAddress,fromAbsPath,toAddress,toAbsPath)
-- TODO: make this use copyContent
if fromAbsPath:sub(-1)=="/" then fromAbsPath=fromAbsPath:sub(1,-2) end
if toAbsPath:sub(-1)=="/" then toAbsPath=toAbsPath:sub(1,-2) end
component.invoke(toAddress,"makeDirectory",toAbsPath)
local fileList,dirList = getRecursiveList(fromAddress,fromAbsPath)
for i=1,#dirList do
component.invoke(toAddress,"makeDirectory",toAbsPath.."/"..dirList[i])
end
for i=1,#fileList do
local fromFile, toFile = fromAbsPath.."/"..fileList[i], toAbsPath.."/"..fileList[i]
--[[ local handle = component.invoke(fromAddress, "open", fromFile, "r")
local data, tmpdata = "", nil
repeat
tmpdata = component.invoke(fromAddress, "read", handle, math.huge or math.maxinteger)
data = data .. (tmpdata or "")
until not tmpdata
tmpdata = component.invoke(fromAddress, "close", handle)
local handle = component.invoke(toAddress, "open", toFile, "w")
component.invoke(toAddress, "write", handle, data)
component.invoke(toAddress, "close", handle) ]]
local fromHandle = component.invoke(fromAddress, "open", fromFile, "r")
local toHandle = component.invoke(toAddress, "open", toFile, "w")
copyContent({
["read"]=function(...) return component.invoke(fromAddress, "read", handle, ...) end,
["close"]=function(...) return component.invoke(fromAddress, "close", handle, ...) end
},{
["write"]=function(...) return component.invoke(fromAddress, "write", handle, ...) end,
["close"]=function(...) return component.invoke(fromAddress, "close", handle, ...) end
})
end
end
function filesystem.isDirectory(path)
checkArg(1, path, "string")
local address, absPath = filesystem.absolutePath(path)
if not address then
return false
end
return component.invoke(address, "isDirectory", absPath)
end
function filesystem.rename(fromPath, toPath)
checkArg(1, fromPath, "string")
checkArg(2, toPath, "string")
local fromAddress, fromAbsPath = filesystem.absolutePath(fromPath)
local toAddress, toAbsPath = filesystem.absolutePath(toPath)
if not fromAddress or not toAddress then
return false
end
if fromAddress == toAddress then
return component.invoke(fromAddress, "rename", fromAbsPath, toAbsPath)
elseif filesystem.isDirectory(fromPath) then -- component.invoke(fromAddress, "isDirectory", fromAbsPath) then
copyRecursive(fromAddress,fromAbsPath,toAddress,toAbsPath)
filesystem.remove(fromPath) -- component.invoke(fromAddress,"remove", fromAbsPath)
else
local handle, data, tmpdata = filesystem.open(fromPath), "", nil -- component.invoke(fromAddress, "open", fromAbsPath, "r"), "", nil
repeat
tmpdata = handle:read(math.huge or math.maxinteger) -- component.invoke(fromAddress, "read", handle, math.huge or math.maxinteger)
data = data .. (tmpdata or "")
until not tmpdata
tmpdata = handle:close() -- component.invoke(fromAddress, "close", handle)
local handle = filesystem.open(toPath) -- component.invoke(toAddress, "open", toAbsPath, "w")
handle:write(data) -- component.invoke(toAddress, "write", handle, data)
handle:close() -- component.invoke(toAddress, "close", handle)
filesystem.remove(fromPath) -- component.invoke(fromAddress, "remove", fromAbsPath)
end
end
function filesystem.copy(fromPath, toPath)
checkArg(1, fromPath, "string")
checkArg(2, toPath, "string")
local fromAddress, fromAbsPath = filesystem.absolutePath(fromPath)
local toAddress, toAbsPath = filesystem.absolutePath(toPath)
if not fromAddress or not toAddress then
return false
end
if filesystem.isDirectory(fromPath) then -- component.invoke(fromAddress, "isDirectory", fromAbsPath)
copyRecursive(fromAddress,fromAbsPath,toAddress,toAbsPath)
else
--[[ local handle = filesystem.open(fromPath,"r")
local data, tmpdata = "", nil
repeat
tmpdata = handle:read(math.huge or math.maxinteger)
data = data .. (tmpdata or "")
until not tmpdata
tmpdata = handle:close()
local handle = filesystem.open(toPath,"w")
handle:write(data)
handle:close() ]]
copyContent(filesystem.open(fromPath,"r"),filesystem.open(toPath,"w"))
end
end
function filesystem.remove(path)
checkArg(1, path, "string")
local address, absPath = filesystem.absolutePath(path)
if not address then
return false
end
if absPath:find("^/special") then return false end
if absPath:find("^/tmp") then return false end
if absPath:find("^/mnt") then return false end
return component.invoke(address, "remove", absPath)
end
function filesystem.makeDirectory(path)
checkArg(1, path, "string")
local address, absPath = filesystem.absolutePath(path)
if not address then
return false
end
return component.invoke(address, "makeDirectory", absPath)
end
return(filesystem)