diff --git a/halyde/kernel/boot.lua b/halyde/kernel/boot.lua index e9aac73..b72e08f 100644 --- a/halyde/kernel/boot.lua +++ b/halyde/kernel/boot.lua @@ -4,60 +4,72 @@ _G._OSVERSION = "HALYDE VERSION" -- TODO: Put this in a separate config file _G._OSLOGO = "" _G._PUBLIC = {} _G._PUBLIC.unicode = assert(loadfile("/lib/unicode.lua")(loadfile)) +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() -_G.package = {["preloaded"] = {}} +log.kernel.info("Loaded OS logo") + +_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?? require("/halyde/kernel/datatools.lua") -- If this is not imported BEFORE modload gets run, modload requires filesystem which requires computer which requires datatools. TODO: When VFS is implemented, make the pre-VFS loading of filesystem load a more basic version. And remove this. +log.kernel.info("Loading modules") require("/halyde/kernel/modload.lua") package.preload("component") package.preload("computer") +log.kernel.info("Pre-loaded low-level packages") local component = require("component") local gpu = component.gpu @@ -65,12 +77,14 @@ local screenAddress = component.list("screen")() gpu.bind(screenAddress) 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") require("/halyde/kernel/tsched.lua") diff --git a/halyde/logs/kernel.log b/halyde/logs/kernel.log new file mode 100644 index 0000000..040ffff --- /dev/null +++ b/halyde/logs/kernel.log @@ -0,0 +1,6 @@ +INFO [1.45] Loaded OS logo +INFO [1.5] Generated userland require function +INFO [1.5] Loading modules +INFO [2.2] Pre-loaded low-level packages +INFO [2.3] Bound GPU to screen d9443671-225d-4637-980f-fad46c9fb845 +INFO [2.3] Starting tsched diff --git a/lib/filesystem.lua b/lib/filesystem.lua index 570e3c5..b44767b 100644 --- a/lib/filesystem.lua +++ b/lib/filesystem.lua @@ -1,405 +1,514 @@ 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 + 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 +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) + 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 readcursorsectorCount 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 readcursorsectorCount 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 + 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") + 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 + -- Calculate current absolute position in file + local currentAbsolutePos = bufferOffset + readCursor - 1 + + -- Seek the underlying file handle to the correct position + component.invoke(self.address, "seek", self.handle, "set", currentAbsolutePos) + + -- Now perform the actual seek + local newPos = component.invoke(self.address, "seek", self.handle, whence, offset) + + -- Invalidate the buffer and reset positions + 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 + 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 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() +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 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) @@ -409,66 +518,74 @@ 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) @@ -478,29 +595,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) +return filesystem diff --git a/lib/log.lua b/lib/log.lua index fe8fb35..78b20f2 100644 --- a/lib/log.lua +++ b/lib/log.lua @@ -1,30 +1,71 @@ -local fs = require("filesystem") -local computer = require("computer") +local fs, computer +if require then + 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 +end local log = {} -function log.add(text, logType) - checkArg(1, text, "string") - checkArg(2, logType, "string", "nil") - if logType ~= "debug" and logType ~= "info" and logType ~= "warning" and logType ~= "error" and logType then - error("Log type must either be debug, info, warning or error.") - end - if not logType then - logType = "debug" - end - local handle = fs.open("/halyde/system.log", "a") - local time = computer.uptime() - local logText = string.format("[%02d:%02d:%02d:%02d] " .. text, math.floor(time / 86400), math.floor(time / 3600 % 24), math.floor(time / 60 % 60), math.floor(time % 60)) .. "\n" - if logType == "debug" then - handle:write("\27[37m" .. logText) - elseif logType == "info" then - handle:write("\27[97m" .. logText) - elseif logType == "warning" then - handle:write("\27[93m" .. logText) - else - handle:write("\27[91m" .. logText) - end - handle:close() -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, +}) return log