628 lines
19 KiB
Lua
628 lines
19 KiB
Lua
local loadfile = ... -- raw loadfile from boot.lua
|
|
local unicode, component, computer
|
|
|
|
local bufferSize = math.huge or math.maxinteger
|
|
|
|
if loadfile then
|
|
unicode = loadfile("/lib/unicode.lua")(loadfile)
|
|
component = loadfile("/lib/component.lua")(loadfile)
|
|
computer = _G.computer
|
|
elseif require then
|
|
unicode = require("unicode")
|
|
component = require("component")
|
|
computer = require("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 bufferOffset = 0 -- Position in file where buffer starts
|
|
local readCursor = 1 -- Position within buffer (1-based)
|
|
|
|
if buffered and mode == "r" then
|
|
content = component.invoke(address, "read", handle, bufferSize) or ""
|
|
bufferOffset = 0
|
|
readCursor = 1
|
|
end
|
|
|
|
function properHandle.read(self, amount)
|
|
checkArg(2, amount, "number")
|
|
if unmanagedDrive then
|
|
-- TODO: Test if this still works
|
|
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
|
|
if amount == math.huge or amount == math.maxinteger then
|
|
-- Read everything remaining
|
|
local result = ""
|
|
|
|
-- First, get what's left in current buffer
|
|
if content and readCursor <= #content then
|
|
result = content:sub(readCursor)
|
|
readCursor = #content + 1
|
|
end
|
|
|
|
-- Then read all remaining data from file
|
|
while true do
|
|
local newData = component.invoke(address, "read", handle, bufferSize)
|
|
if not newData or newData == "" then
|
|
break
|
|
end
|
|
result = result .. newData
|
|
end
|
|
|
|
-- Update buffer state
|
|
content = nil
|
|
bufferOffset = bufferOffset + #(content or "")
|
|
|
|
return result ~= "" and result or nil
|
|
else
|
|
local result = ""
|
|
local remaining = amount
|
|
|
|
while remaining > 0 do
|
|
-- If we need more data or buffer is empty
|
|
if not content or readCursor > #content then
|
|
content = component.invoke(address, "read", handle, bufferSize)
|
|
if not content or content == "" then
|
|
break
|
|
end
|
|
bufferOffset = bufferOffset + (readCursor - 1)
|
|
readCursor = 1
|
|
end
|
|
|
|
-- Extract data from current buffer
|
|
local available = #content - readCursor + 1
|
|
local toRead = math.min(remaining, available)
|
|
local chunk = content:sub(readCursor, readCursor + toRead - 1)
|
|
|
|
result = result .. chunk
|
|
readCursor = readCursor + toRead
|
|
remaining = remaining - toRead
|
|
end
|
|
|
|
return result ~= "" and result or nil
|
|
end
|
|
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
|
|
end
|
|
return component.invoke(self.address, "close", self.handle)
|
|
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
|
|
function properHandle.seek(self, whence, offset)
|
|
checkArg(2, whence, "string", "number", "nil")
|
|
checkArg(3, offset, "number", "nil")
|
|
if not offset then
|
|
offset = 0
|
|
end
|
|
if type(whence) == "number" then
|
|
offset = whence
|
|
end
|
|
if not whence or type(whence) == "number" then
|
|
whence = "cur"
|
|
end
|
|
|
|
if buffered then
|
|
local currentAbsolutePos = bufferOffset + readCursor - 1
|
|
-- Seek real handle position to buffer handle position
|
|
component.invoke(self.address, "seek", self.handle, "set", currentAbsolutePos)
|
|
local newPos = component.invoke(self.address, "seek", self.handle, whence, math.max(offset, -currentAbsolutePos))
|
|
content = nil
|
|
bufferOffset = newPos or 0
|
|
readCursor = 1
|
|
return newPos
|
|
else
|
|
return component.invoke(self.address, "seek", self.handle, whence, offset)
|
|
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
|