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.basename(path) checkArg(1, path, "string") return path:match("/([^/]+)/?$") or "" 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.parent(path) checkArg(1, path, "string") local p = filesystem.canonical(path) -- return "/" on "/" return p == "/" and "/" or (p:match("^(.*)/[^/]+/?$") or "/") 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 while true do local tmpdata = fromHandle.read(2048) 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) 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 fromHandle = component.invoke(fromAddress, "open", fromFile, "r") local toHandle = component.invoke(toAddress, "open", toFile, "w") copyContent({ ["read"] = function(...) return component.invoke(fromAddress, "read", fromHandle, ...) end, ["close"] = function(...) return component.invoke(fromAddress, "close", fromHandle, ...) end, }, { ["write"] = function(...) return component.invoke(toAddress, "write", toHandle, ...) end, ["close"] = function(...) return component.invoke(toAddress, "close", toHandle, ...) 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.isFile(path) return not filesystem.isDirectory(path) and filesystem.exists(path) 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