diff --git a/halyde/kernel/boot.lua b/halyde/kernel/boot.lua index b72e08f..7803964 100644 --- a/halyde/kernel/boot.lua +++ b/halyde/kernel/boot.lua @@ -8,8 +8,8 @@ local log = assert(loadfile("/lib/log.lua")(loadfile)) local handle, tmpdata = filesystem.open("/halyde/config/oslogo.ans", "r"), nil repeat - tmpdata = handle:read(math.huge) - _OSLOGO = _OSLOGO .. (tmpdata or "") + tmpdata = handle:read(math.huge) + _OSLOGO = _OSLOGO .. (tmpdata or "") until not tmpdata handle:close() @@ -20,46 +20,46 @@ _G.package = { ["preloaded"] = {} } loadfile("/halyde/kernel/modules/datatools.lua")() function _G.reqgen(load) - return function(module, ...) - local args = table.pack(...) - if package.preloaded[module] then - return package.preloaded[module] - end - local modulepath - if filesystem.exists(module) then - modulepath = module - elseif filesystem.exists("/lib/" .. module .. ".lua") then - modulepath = "/lib/" .. module .. ".lua" - elseif - shell - and shell.workingDirectory - and filesystem.exists(filesystem.concat(shell.workingDirectory, module .. ".lua")) - then - modulepath = shell.workingDirectory .. module .. ".lua" - end - assert(modulepath, "Module not found\nPossible locations:\n/lib/" .. module .. ".lua") - local handle, data, tmpdata = filesystem.open(modulepath), "", nil - repeat - tmpdata = handle:read(math.huge or math.maxinteger) - data = data .. (tmpdata or "") - until not tmpdata - handle:close() - return (assert(load(data, "=" .. modulepath))(table.unpack(args))) - end + return function(module, ...) + local args = table.pack(...) + if package.preloaded[module] then + return package.preloaded[module] + end + local modulepath + if filesystem.exists(module) then + modulepath = module + elseif filesystem.exists("/lib/" .. module .. ".lua") then + modulepath = "/lib/" .. module .. ".lua" + elseif + shell + and shell.workingDirectory + and filesystem.exists(filesystem.concat(shell.workingDirectory, module .. ".lua")) + then + modulepath = shell.workingDirectory .. module .. ".lua" + end + assert(modulepath, "Module not found\nPossible locations:\n/lib/" .. module .. ".lua") + local handle, data, tmpdata = filesystem.open(modulepath), "", nil + repeat + tmpdata = handle:read(math.huge or math.maxinteger) + data = data .. (tmpdata or "") + until not tmpdata + handle:close() + return (assert(load(data, "=" .. modulepath))(table.unpack(args))) + end end _G.require = reqgen(_G.load) log.kernel.info("Generated userland require function") function _G.package.preload(module) - local handle, data, tmpdata = assert(filesystem.open("/lib/" .. module .. ".lua", "r")), "", nil - repeat - tmpdata = handle:read(math.huge or math.maxinteger) - data = data .. (tmpdata or "") - until not tmpdata - handle:close() - package.preloaded[module] = assert(load(data, "=" .. module))() - _G[module] = nil + local handle, data, tmpdata = assert(filesystem.open("/lib/" .. module .. ".lua", "r")), "", nil + repeat + tmpdata = handle:read(math.huge or math.maxinteger) + data = data .. (tmpdata or "") + until not tmpdata + handle:close() + package.preloaded[module] = assert(load(data, "=" .. module))() + _G[module] = nil end -- Datatools is imported twice?? @@ -80,10 +80,10 @@ gpu.setResolution(gpu.maxResolution()) log.kernel.info("Bound GPU to screen " .. tostring(screenAddress)) if not filesystem.exists("/halyde/config/shell.json") then -- Auto-generate configs - filesystem.copy("/halyde/config/generate/shell.json", "/halyde/config/shell.json") + filesystem.copy("/halyde/config/generate/shell.json", "/halyde/config/shell.json") end if not filesystem.exists("/halyde/config/startupapps.json") then - filesystem.copy("/halyde/config/generate/startupapps.json", "/halyde/config/startupapps.json") + filesystem.copy("/halyde/config/generate/startupapps.json", "/halyde/config/startupapps.json") end log.kernel.info("Starting tsched") diff --git a/lib/filesystem.lua b/lib/filesystem.lua index b44767b..b7558cb 100644 --- a/lib/filesystem.lua +++ b/lib/filesystem.lua @@ -4,511 +4,512 @@ 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 + 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") + 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, "/") + 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 + 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 + 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) + 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 + 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) + 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 + 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)) + 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 + 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) + 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 + 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 = "" + 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 + -- 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 + -- 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 "") + -- Update buffer state + content = nil + bufferOffset = bufferOffset + #(content or "") - return result ~= "" and result or nil - else - local result = "" - local remaining = amount + 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 + 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) + -- 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 + 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") - 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 + return result ~= "" and result or nil + end + else + return component.invoke(self.address, "read", self.handle, amount) + end + end + end - if buffered then - -- Calculate current absolute position in file - local currentAbsolutePos = bufferOffset + readCursor - 1 + 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 - -- Seek the underlying file handle to the correct position - component.invoke(self.address, "seek", self.handle, "set", currentAbsolutePos) + function properHandle.close(self) + if buffered then + content = nil + end + return component.invoke(self.address, "close", self.handle) + end - -- Now perform the actual seek - local newPos = component.invoke(self.address, "seek", self.handle, whence, offset) + 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 - -- Invalidate the buffer and reset positions - content = nil - bufferOffset = newPos or 0 - readCursor = 1 + function properHandle.close(self) + return eeprom[setFunc](content) + end + end + end + end + function properHandle.seek(self, whence, offset) + checkArg(2, whence, "string", "number") + 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 - return newPos - else - return component.invoke(self.address, "seek", self.handle, whence, offset) - end - end - return properHandle + 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, math.max(offset, -currentAbsolutePos)) + 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 + 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) + 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 + 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() + 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") + -- 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) @@ -518,74 +519,75 @@ local function copyRecursive(fromAddress, fromAbsPath, toAddress, toAbsPath) 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 + 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) + 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 + 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") + 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) @@ -595,35 +597,35 @@ function filesystem.copy(fromPath, toPath) local handle = filesystem.open(toPath,"w") handle:write(data) handle:close() ]] - copyContent(filesystem.open(fromPath, "r"), filesystem.open(toPath, "w")) - end + 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) + 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) + 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 diff --git a/lib/log.lua b/lib/log.lua index 78b20f2..3607c1c 100644 --- a/lib/log.lua +++ b/lib/log.lua @@ -1,71 +1,85 @@ local fs, computer +local chunkSize = 1024 if require then - fs = require("filesystem") - computer = require("computer") + fs = require("filesystem") + computer = require("computer") else - local loadfile = ... - fs = loadfile("/lib/filesystem.lua")(loadfile) - computer = _G.computer -end - -logFileSizeLimit = 16384 - -local function writeToLog(path, text) - local handle - if fs.exists(path) then - handle = assert(fs.open(path, "a")) - else - handle = assert(fs.open(path, "w")) - end - handle:write(text .. "\n") - handle:close() - - -- Log trimming if it gets too long - if fs.size(path) > logFileSizeLimit then - ocelot.log("Trimming log...") - local newlineCounter = 0 - local sizeCounter = 0 - local readHandle = fs.open(path, "r") - local chunkSize = 1024 - readHandle:seek("end", -chunkSize) - repeat - local readText = readHandle:read(chunkSize) - readHandle:seek(-chunkSize * 2) - local _, newlineCount = readText:gsub("\n", "\n") - newlineCounter = newlineCounter + newlineCount - sizeCounter = sizeCounter + chunkSize - until sizeCounter >= logFileSizeLimit * 0.75 - readHandle:seek(chunkSize) - local writeHandle = fs.open(path, "w") - while true do - local tmpdata = readHandle:read(math.huge or math.maxinteger) - if not tmpdata then - break - end - writeHandle:write(tmpdata) - end - readHandle:close() - writeHandle:close() - end + local loadfile = ... + fs = loadfile("/lib/filesystem.lua")(loadfile) + computer = _G.computer end local log = {} +local logFileSizeLimit = 16384 + +local function writeToLog(path, text) + local handle + if fs.exists(path) then + handle = assert(fs.open(path, "a")) + else + handle = assert(fs.open(path, "w")) + end + handle:write(text .. "\n") + handle:close() + + -- Log trimming if it gets too long + if fs.size(path) > logFileSizeLimit then + local sizeCounter = 0 + local readHandle = fs.open(path, "r") + local currentChunk = "" + readHandle:seek("end", -chunkSize) + repeat + currentChunk = readHandle:read(chunkSize) + readHandle:seek(-chunkSize * 2) + sizeCounter = sizeCounter + chunkSize + until sizeCounter >= logFileSizeLimit * 0.75 + while true do + local infoEntry = currentChunk:find("INFO [", 1, true) + local warnEntry = currentChunk:find("WARN [", 1, true) + local errorEntry = currentChunk:find("ERROR [", 1, true) + if not infoEntry and not warnEntry and not errorEntry then + readHandle:seek(-chunkSize) + else + readHandle:seek(math.min(infoEntry or math.huge or math.maxinteger, warnEntry or math.huge or math.maxinteger, + errorEntry or math.huge or math.maxinteger) - 1) + break + end + if readHandle:seek("cur") == 0 then -- Failsafe to prevent infinite loops + break + end + end + local writeHandle = fs.open(path, "w") + while true do + local tmpdata = readHandle:read(math.huge or math.maxinteger) + if not tmpdata then + break + end + writeHandle:write(tmpdata) + end + readHandle:close() + writeHandle:close() + end +end + setmetatable(log, { - ["__index"] = function(tab, index) - return { - ["logpath"] = fs.concat("/halyde/logs/", index .. ".log"), - ["info"] = function(text) - writeToLog(fs.concat("/halyde/logs/", index .. ".log"), "INFO [" .. computer.uptime() .. "] " .. text) - end, - ["warn"] = function(text) - writeToLog(fs.concat("/halyde/logs/", index .. ".log"), "WARN [" .. computer.uptime() .. "] " .. text) - end, - ["error"] = function(text) - writeToLog(fs.concat("/halyde/logs/", index .. ".log"), "ERROR [" .. computer.uptime() .. "] " .. text) - end, - } - end, + ["__index"] = function(_, index) + return { + ["logpath"] = fs.concat("/halyde/logs/", index .. ".log"), + ["info"] = function(text) + writeToLog(fs.concat("/halyde/logs/", index .. ".log"), + "INFO [" .. string.format("%.2f", computer.uptime()) .. "] " .. text) + end, + ["warn"] = function(text) + writeToLog(fs.concat("/halyde/logs/", index .. ".log"), + "WARN [" .. string.format("%.2f", computer.uptime()) .. "] " .. text) + end, + ["error"] = function(text) + writeToLog(fs.concat("/halyde/logs/", index .. ".log"), + "ERROR [" .. string.format("%.2f", computer.uptime()) .. "] " .. text) + end, + } + end, }) return log