DistFS/distfs.filesystem.lua
2017-02-05 22:54:11 -08:00

303 lines
7.7 KiB
Lua

local event = require("event")
local component = require("component")
local io = require("io")
local os = require("os")
fs = {}
-- Default configuration
fs.raidsOnly = true
fs.allowHotplug = true
fs.arrayMaster = false
fs.bootSync = false
fs.addSync = false
function fs.componentAdded(eventId, componentPath, componentType)
if componentType == "filesystem" then
for i = 1, #fs.filesystems do
if fs.filesystems[i].address == componentPath then
return -- Don't re-add filesystems if they're already in.
end
end
local sys = component.proxy(componentPath)
if (not fs.raidsOnly) or sys.getLabel() == "raid" then
table.insert(fs.filesystems, sys)
if eventId == "component_added" and fs.arrayMaster and fs.addSync then
fs.syncFilesystems()
end
if eventId == "init" then
--io.write("> .. Mounted FS " .. componentPath .. "\n")
end
end
end
end
function fs.syncFilesystems()
if not fs.arrayMaster then return false, "Not array master" end
local folderList = { "/" }
-- Build a list of all folders on all drives
for i = 1,#folderList do
local path = folderList[i]
local search = {}
for _,system in pairs(fs.filesystems) do
local sysFiles = system.list(path)
for _,file in ipairs(sysFiles) do
local filePath = path..file
if system.isDirectory(filePath) then
if not fs.contains(search, filePath) then
table.insert(search, filePath)
table.insert(folderList, filePath)
end
end
end
--os.sleep(0.05)
end
end
-- Ensure each of these folders exists on every drive
for _, folder in ipairs(folderList) do
for _, system in ipairs(fs.filesystems) do
system.makeDirectory(folder)
end
--os.sleep(0.05)
end
return true
end
function fs.init()
fs.handles = {}
fs.filesystems = {}
--io.write("> . Performing DistFS initialization...\n")
for k,_ in pairs(component.list("filesystem")) do
fs.componentAdded("init", k, "filesystem")
end
if fs.allowHotplug then event.listen("component_added", fs.componentAdded) end
if fs.bootSync and fs.arrayMaster then
--io.write("> . Performing DistFS synchronization...\n")
fs.syncFilesystems()
end
--io.write("> . DistFS ready! Spanning " .. #fs.filesystems .. " volumes.\n")
end
function fs.contains(haystack, needle)
for i=1,#haystack do
if haystack[i] == needle then return true end
end
return false
end
function fs.merge(outTable, table2)
for src = 1, #table2 do
if not fs.contains(outTable, table2[src]) then table.insert(outTable, table2[src]) end
end
end
function fs.mostFreeSpace()
-- Return whichever FS has the most free space available
local avail = 0
local sys = nil
for _,system in pairs(fs.filesystems) do
local a = system.spaceTotal() - system.spaceUsed()
if a > avail then
avail = a
sys = system
end
end
return sys
end
function fs.findFile(path)
-- Search through filesystems until I find the file we're looking for and return the filesystem containing it
for _, sys in pairs(fs.filesystems) do
if sys.exists(path) then return sys end
end
return nil
end
-- Proxy Objects for FS Component --
fs.proxy = {}
fs.proxy.address = "62645f02-a97d-4e3a-8c1d-7f88f4dacbc2"
function fs.proxy.spaceUsed()
local used = 0
for _,system in pairs(fs.filesystems) do
used = used + system.spaceUsed()
end
return used
end
function fs.proxy.open(path, mode)
if #fs.filesystems == 0 then return nil, "No spanned volumes" end
-- Either open the file on its existing filesystem, create it on the FS with the most free space, or fail if we're trying to open something non-existent for reading
checkArg(1, path, "string")
checkArg(2, mode, "string", "nil")
mode = mode or "r"
local bRead = mode:match("[ra]")
local bWrite = mode:match("[wa]")
if not bRead and not bWrite then
return nil, "invalid mode"
end
local system = fs.findFile(path)
local handle, err
if system ~= nil then
handle, err = system.open(path, mode)
else
if bRead then
return nil, path
end
system = fs.mostFreeSpace()
handle, err = system.open(path, mode)
end
-- Associate the file handle with the FS in fs.handles
if handle ~= nil then
fs.handles[handle] = system
end
return handle, err
end
function fs.proxy.seek(handle, whence, offset)
if fs.handles[handle] == nil then
return false, "unknown file handle"
end
fs.handles[handle].seek(handle, whence, offset)
end
function fs.proxy.makeDirectory(path)
if #fs.filesystems == 0 then return false, "No spanned volumes" end
local okay = true
for _,system in pairs(fs.filesystems) do
okay = okay and ((not system.exists(path)) or system.isDirectory(path))
end
if not okay then return false end
for _,system in pairs(fs.filesystems) do
okay = okay and (system.isDirectory(path) or system.makeDirectory(path))
end
return okay
end
function fs.proxy.exists(path)
if #fs.filesystems == 0 then return nil, "No spanned volumes" end
for _,system in pairs(fs.filesystems) do
if system.exists(path) then return true end
end
return false
end
function fs.proxy.isReadOnly()
return false
end
function fs.proxy.write(handle, value)
if #fs.filesystems == 0 then return nil, "No spanned volumes" end
if fs.handles[handle] == nil then
return false, "unknown file handle"
end
return fs.handles[handle].write(handle, value)
end
function fs.proxy.spaceTotal()
local total = 0
for _,system in pairs(fs.filesystems) do
total = total + system.spaceTotal()
end
return total
end
function fs.proxy.isDirectory(path)
if #fs.filesystems == 0 then return nil, "No spanned volumes" end
return fs.filesystems[1].isDirectory(path) -- this relies on the filesystems being in sync
end
function fs.proxy.rename(from, to)
if fs.proxy.exists(to) then
return false, "target file already exists"
else
local srcsys = fs.findFile(from)
if srcsys == nil then return false, "source file doesn't exist" end
return srcsys.rename(from, to)
end
end
function fs.proxy.list(path)
if #fs.filesystems == 0 then return nil, "No spanned volumes" end
local list = {}
-- Get a list of all files from every fs for this folder and return it
for _, sys in pairs(fs.filesystems) do
local add = sys.list(path)
if add ~= nil then
fs.merge(list, sys.list(path))
end
end
if #list == 0 then return nil end
return list
end
function fs.proxy.lastModified(path)
if #fs.filesystems == 0 then return nil, "No spanned volumes" end
local sys = fs.findFile(path)
if sys == nil then return nil, "file doesn't exist" end
return sys.lastModified(path)
end
function fs.proxy.getLabel()
return "DistFS"
end
function fs.proxy.remove(path)
if #fs.filesystems == 0 then return false, "No spanned volumes" end
local sys = fs.findFile(path)
if sys == nil then return false, "file doesn't exist" end
return sys.remove(path)
end
function fs.proxy.close(handle)
if fs.handles[handle] == nil then
return false, "unknown file handle"
end
local a, b = fs.handles[handle].close(handle)
if a then fs.handles[handle] = nil end
return a, b
end
function fs.proxy.size(path)
if #fs.filesystems == 0 then return nil, "No spanned volumes" end
local sys = fs.findFile(path)
if sys == nil then return nil, "file doesn't exist" end
return sys.size(path)
end
function fs.proxy.read(handle, count)
if fs.handles[handle] == nil then
return false, "unknown file handle"
end
return fs.handles[handle].read(handle, count)
end
function fs.proxy.setLabel(newLabel)
return fs.proxy.getLabel() -- No.
end
-- Extra utility functions
function fs.proxy.sync()
fs.syncFilesystems()
end
return fs