617 lines
14 KiB
Lua
617 lines
14 KiB
Lua
|
if fs.native then
|
||
|
return false, "VFS already installed"
|
||
|
end
|
||
|
|
||
|
local function checkArg(argtype, argn, arg)
|
||
|
if type(arg) ~= argtype then
|
||
|
error("invalid arg #" .. argn .. " (" .. argtype .. " expected, got " .. type(arg) .. ")", 1)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local proxy = {}
|
||
|
local nativefs = fs; _G.fs = {}
|
||
|
local rootfs = nativefs
|
||
|
function fs.native()
|
||
|
return nativefs
|
||
|
end
|
||
|
function fs.setRoot(_proxy)
|
||
|
rootfs = _proxy
|
||
|
end
|
||
|
function fs.getRoot()
|
||
|
return rootfs
|
||
|
end
|
||
|
function fs.cleanPath(path)
|
||
|
local parts = {}
|
||
|
for match in string.gmatch(path, "[^/]+") do
|
||
|
if match == ".." then
|
||
|
if #parts < 1 then
|
||
|
error("Invalid path", 1)
|
||
|
end
|
||
|
table.remove(parts, #parts)
|
||
|
else
|
||
|
table.insert(parts, match)
|
||
|
end
|
||
|
end
|
||
|
return table.concat(parts, "/")
|
||
|
end
|
||
|
function fs.proxy(path, _proxy)
|
||
|
checkArg("string", 1, path)
|
||
|
checkArg("table", 2, _proxy)
|
||
|
path = fs.cleanPath(path)
|
||
|
if proxy[path] then
|
||
|
error("proxy already present at path", 1)
|
||
|
end
|
||
|
proxy[path] = _proxy
|
||
|
end
|
||
|
function fs.unproxy(path)
|
||
|
path = fs.cleanPath(path)
|
||
|
proxy[path] = nil
|
||
|
end
|
||
|
function fs.getProxy(path)
|
||
|
local p, pathbase = rootfs, ""
|
||
|
local depth = 0
|
||
|
for k, v in pairs(proxy) do
|
||
|
local _depth = 0
|
||
|
for _ in string.gmatch(k, "[/]+") do
|
||
|
_depth = _depth + 1
|
||
|
end
|
||
|
if (path == k or path:sub(1, #k + 1) == k .. "/") and _depth >= depth then
|
||
|
p = v
|
||
|
pathbase = k
|
||
|
depth = _depth
|
||
|
end
|
||
|
end
|
||
|
return p, pathbase
|
||
|
end
|
||
|
function fs.getProxiedPath(path)
|
||
|
local str = string.sub(path, #({fs.getProxy(path)})[2] + 1)
|
||
|
return fs.cleanPath(str)
|
||
|
end
|
||
|
function fs.listProxy()
|
||
|
local t = {}
|
||
|
for path in pairs(proxy) do
|
||
|
table.insert(t, path)
|
||
|
end
|
||
|
table.sort(t)
|
||
|
return t
|
||
|
end
|
||
|
function fs.list(path)
|
||
|
path = fs.cleanPath(path)
|
||
|
return fs.getProxy(path).list(fs.getProxiedPath(path))
|
||
|
end
|
||
|
function fs.move(path, target)
|
||
|
path = fs.cleanPath(path)
|
||
|
target = fs.cleanPath(target)
|
||
|
local p1 = fs.getProxy(path)
|
||
|
local p2 = fs.getProxy(target)
|
||
|
if p1 ~= p2 then
|
||
|
if p1.isDir(fs.getProxiedPath(path)) then
|
||
|
fs.copy(path, target)
|
||
|
p1.delete(fs.getProxiedPath(path))
|
||
|
return
|
||
|
end
|
||
|
if p2.exists(fs.getProxiedPath(target)) then
|
||
|
error("File exists", 1)
|
||
|
end
|
||
|
if p1.isReadOnly(fs.getProxiedPath(path)) or p2.isReadOnly(fs.getProxiedPath(target)) then
|
||
|
error("Access denied", 1)
|
||
|
end
|
||
|
local f1 = p1.open(fs.getProxiedPath(path), "r")
|
||
|
local f2 = p2.open(fs.getProxiedPath(path), "w")
|
||
|
f2.write(f1.readAll())
|
||
|
f1.close()
|
||
|
f2.close()
|
||
|
p1.delete(fs.getProxiedPath(path))
|
||
|
else
|
||
|
p1.move(fs.getProxiedPath(path), fs.getProxiedPath(target))
|
||
|
end
|
||
|
end
|
||
|
function fs.recursiveCopy(p1, p2, pA, pB)
|
||
|
for i, v in ipairs(p1.list(pA)) do
|
||
|
if p1.isDir(fs.combine(pA, v)) then
|
||
|
if not p2.exists(fs.combine(pB, v)) then
|
||
|
p2.makeDir(fs.combine(pB, v))
|
||
|
end
|
||
|
fs.recursiveCopy(p1, p2, fs.combine(pA, v), fs.combine(pB, v))
|
||
|
else
|
||
|
local f1 = p1.open(fs.combine(pA, v), "r")
|
||
|
local f2 = p2.open(fs.combine(pB, v), "w")
|
||
|
f2.write(f1.readAll())
|
||
|
f1.close()
|
||
|
f2.close()
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
function fs.copy(path, target)
|
||
|
path = fs.cleanPath(path)
|
||
|
target = fs.cleanPath(target)
|
||
|
local p1 = fs.getProxy(path)
|
||
|
local p2 = fs.getProxy(target)
|
||
|
if p1 ~= p2 then
|
||
|
if p1.isDir(fs.getProxiedPath(path)) then
|
||
|
fs.recursiveCopy(p1, p2, fs.getProxiedPath(path), fs.getProxiedPath(target))
|
||
|
return
|
||
|
end
|
||
|
if p2.exists(fs.getProxiedPath(target)) then
|
||
|
error("File exists", 1)
|
||
|
end
|
||
|
if p2.isReadOnly(fs.getProxiedPath(target)) then
|
||
|
error("Access denied", 1)
|
||
|
end
|
||
|
local f1 = p1.open(fs.getProxiedPath(path), "r")
|
||
|
local f2 = p2.open(fs.getProxiedPath(target), "w")
|
||
|
f2.write(f1.readAll())
|
||
|
f1.close()
|
||
|
f2.close()
|
||
|
else
|
||
|
p1.copy(fs.getProxiedPath(path), fs.getProxiedPath(target))
|
||
|
end
|
||
|
end
|
||
|
function fs.makeDir(path)
|
||
|
path = fs.cleanPath(path)
|
||
|
fs.getProxy(path).makeDir(fs.getProxiedPath(path))
|
||
|
end
|
||
|
function fs.delete(path)
|
||
|
path = fs.cleanPath(path)
|
||
|
fs.getProxy(path).delete(fs.getProxiedPath(path))
|
||
|
end
|
||
|
function fs.open(path, mode)
|
||
|
path = fs.cleanPath(path)
|
||
|
return fs.getProxy(path).open(fs.getProxiedPath(path), mode)
|
||
|
end
|
||
|
function fs.isDir(path)
|
||
|
path = fs.cleanPath(path)
|
||
|
return fs.getProxy(path).isDir(fs.getProxiedPath(path))
|
||
|
end
|
||
|
function fs.exists(path)
|
||
|
path = fs.cleanPath(path)
|
||
|
return fs.getProxy(path).exists(fs.getProxiedPath(path))
|
||
|
end
|
||
|
function fs.combine(path1, path2)
|
||
|
return nativefs.combine(path1, path2)
|
||
|
end
|
||
|
function fs.getName(path)
|
||
|
path = fs.cleanPath(path)
|
||
|
return nativefs.getName(path)
|
||
|
end
|
||
|
function fs.getDir(path)
|
||
|
path = fs.cleanPath(path)
|
||
|
return nativefs.getDir(path)
|
||
|
end
|
||
|
function fs.isReadOnly(path)
|
||
|
path = fs.cleanPath(path)
|
||
|
return fs.getProxy(path).isReadOnly(fs.getProxiedPath(path))
|
||
|
end
|
||
|
function fs.find(path) -- cclite copypaste
|
||
|
path = fs.cleanPath(path)
|
||
|
local function recurse_spec(results, path, spec)
|
||
|
local segment = spec:match('([^/]*)'):gsub('/', '')
|
||
|
local pattern = '^' .. segment:gsub("[%.%[%]%(%)%%%+%-%?%^%$]","%%%1"):gsub("%z","%%z"):gsub("%*","[^/]-") .. '$'
|
||
|
if fs.isDir(path) then
|
||
|
for _, file in ipairs(fs.list(path)) do
|
||
|
if file:match(pattern) then
|
||
|
local f = fs.combine(path, file)
|
||
|
if spec == segment then
|
||
|
table.insert(results, f)
|
||
|
end
|
||
|
if fs.isDir(f) then
|
||
|
recurse_spec(results, f, spec:sub(#segment + 2))
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
local results = {}
|
||
|
recurse_spec(results, '', path)
|
||
|
table.sort(results)
|
||
|
return results
|
||
|
end
|
||
|
function fs.getSize(path)
|
||
|
path = fs.cleanPath(path)
|
||
|
return fs.getProxy(path).getSize(fs.getProxiedPath(path))
|
||
|
end
|
||
|
fs.complete = nativefs.complete
|
||
|
function fs.getDrive(path)
|
||
|
path = fs.cleanPath(path)
|
||
|
if fs.getProxy(path).getDrive then
|
||
|
return fs.getProxy(path).getDrive(fs.getProxiedPath(path))
|
||
|
else
|
||
|
return "???"
|
||
|
end
|
||
|
end
|
||
|
function fs.getFreeSpace(path)
|
||
|
path = fs.cleanPath(path)
|
||
|
if fs.getProxy(path).getFreeSpace then
|
||
|
return fs.getProxy(path).getFreeSpace(fs.getProxiedPath(path))
|
||
|
else
|
||
|
return 2^31 - 1
|
||
|
end
|
||
|
end
|
||
|
for i in pairs(nativefs) do
|
||
|
if not fs[i] then
|
||
|
print("MISSING: " .. i)
|
||
|
end
|
||
|
end
|
||
|
function fs.appfsProxy(structure)
|
||
|
local function getFile(path)
|
||
|
local file = structure
|
||
|
for part in string.gmatch(path, "[^/]+") do
|
||
|
if not file.files[part] then
|
||
|
return nil
|
||
|
end
|
||
|
file = file.files[part]
|
||
|
end
|
||
|
return file
|
||
|
end
|
||
|
local p = {}
|
||
|
function p.getDrive()
|
||
|
return "sys"
|
||
|
end
|
||
|
function p.getFreeSpace()
|
||
|
return 0
|
||
|
end
|
||
|
function p.exists(path)
|
||
|
local file = structure
|
||
|
for part in string.gmatch(path, "[^/]+") do
|
||
|
if not file.files[part] then
|
||
|
return false
|
||
|
end
|
||
|
file = file.files[part]
|
||
|
end
|
||
|
return file ~= nil
|
||
|
end
|
||
|
function p.list(path)
|
||
|
local file = getFile(path)
|
||
|
if not file then
|
||
|
error("Not a directory", 1)
|
||
|
end
|
||
|
if not file.files then
|
||
|
error("Not a directory", 1)
|
||
|
end
|
||
|
local files = {}
|
||
|
for f in pairs(file.files) do
|
||
|
table.insert(files, f)
|
||
|
end
|
||
|
table.sort(files)
|
||
|
return files
|
||
|
end
|
||
|
function p.delete(path)
|
||
|
if structure.noDelete then
|
||
|
error("Access denied", 1)
|
||
|
end
|
||
|
end
|
||
|
function p.isDir(path)
|
||
|
if not getFile(path) then
|
||
|
return false
|
||
|
end
|
||
|
return getFile(path).files ~= nil
|
||
|
end
|
||
|
function p.isReadOnly(path)
|
||
|
return getFile(path).ro == true
|
||
|
end
|
||
|
function p.open(path, mode)
|
||
|
local f = getFile(path)
|
||
|
if mode == "r" then
|
||
|
local handle = {closed = false, buffer = f.read and f.read() or ""}
|
||
|
function handle.readLine()
|
||
|
if handle.closed then
|
||
|
return
|
||
|
end
|
||
|
if #handle.buffer < 1 then
|
||
|
return nil
|
||
|
end
|
||
|
local tmp = ""
|
||
|
while #handle.buffer > 0 do
|
||
|
local chr = handle.buffer:sub(1, 1)
|
||
|
handle.buffer = handle.buffer:sub(2)
|
||
|
if chr == "\n" then
|
||
|
return tmp
|
||
|
else
|
||
|
tmp = tmp .. chr
|
||
|
end
|
||
|
end
|
||
|
return tmp
|
||
|
end
|
||
|
function handle.readAll()
|
||
|
if handle.closed then
|
||
|
return
|
||
|
end
|
||
|
local str = handle.buffer
|
||
|
handle.buffer = ""
|
||
|
return str
|
||
|
end
|
||
|
function handle.close()
|
||
|
handle.closed = true
|
||
|
end
|
||
|
return handle
|
||
|
elseif mode == "w" then
|
||
|
local handle = {closed = false, buffer = ""}
|
||
|
function handle.write(str)
|
||
|
if handle.closed then
|
||
|
return
|
||
|
end
|
||
|
handle.buffer = handle.buffer .. str
|
||
|
end
|
||
|
function handle.writeLine(str)
|
||
|
handle.write(str .. "\n")
|
||
|
end
|
||
|
function handle.close()
|
||
|
if handle.closed then
|
||
|
return
|
||
|
end
|
||
|
handle.closed = true
|
||
|
f.write(handle.buffer)
|
||
|
end
|
||
|
return handle
|
||
|
else
|
||
|
error("Not supported", 1)
|
||
|
end
|
||
|
end
|
||
|
function p.getSize()
|
||
|
return 0
|
||
|
end
|
||
|
return p
|
||
|
end
|
||
|
function fs.tmpfsProxy(structure, writeCallback, driveName)
|
||
|
if not structure then
|
||
|
structure = {files = {}}
|
||
|
elseif type(structure) == "table" and not structure.files then
|
||
|
structure.files = {}
|
||
|
end
|
||
|
local function getFile(path)
|
||
|
local file = structure
|
||
|
for part in string.gmatch(path, "[^/]+") do
|
||
|
if not file.files[part] then
|
||
|
error("Not a directory", 1)
|
||
|
end
|
||
|
file = file.files[part]
|
||
|
end
|
||
|
return file
|
||
|
end
|
||
|
local calculateUsed
|
||
|
function calculateUsed(file)
|
||
|
return #textutils.serialize(file)
|
||
|
end
|
||
|
local p = {}
|
||
|
function p.import(newstructure)
|
||
|
structure = newstructure
|
||
|
end
|
||
|
function p.dump()
|
||
|
return structure
|
||
|
end
|
||
|
function p.getDrive()
|
||
|
return driveName or "tmpfs"
|
||
|
end
|
||
|
function p.getFreeSpace()
|
||
|
return -calculateUsed(structure)
|
||
|
end
|
||
|
function p.isReadOnly()
|
||
|
return false
|
||
|
end
|
||
|
function p.exists(path)
|
||
|
local file = structure
|
||
|
for part in string.gmatch(path, "[^/]+") do
|
||
|
if not file.files[part] then
|
||
|
return false
|
||
|
end
|
||
|
file = file.files[part]
|
||
|
end
|
||
|
return file ~= nil
|
||
|
end
|
||
|
function p.isDir(path)
|
||
|
if path == "" then
|
||
|
return true
|
||
|
end
|
||
|
return p.exists(path) and getFile(path).files ~= nil
|
||
|
end
|
||
|
function p.list(path)
|
||
|
local file
|
||
|
if path == "" then
|
||
|
file = structure
|
||
|
else
|
||
|
file = getFile(path)
|
||
|
end
|
||
|
if not file.files then
|
||
|
error("Not a directory", 1)
|
||
|
end
|
||
|
local files = {}
|
||
|
for f in pairs(file.files) do
|
||
|
table.insert(files, f)
|
||
|
end
|
||
|
table.sort(files)
|
||
|
return files
|
||
|
end
|
||
|
function p.delete(path)
|
||
|
if path == "" then
|
||
|
error("Proxy at path", 1)
|
||
|
end
|
||
|
local parent = fs.getDir(path)
|
||
|
local dir
|
||
|
if parent == "" then
|
||
|
dir = structure
|
||
|
else
|
||
|
dir = getFile(parent)
|
||
|
end
|
||
|
local name = fs.getName(path)
|
||
|
dir.files[name] = nil
|
||
|
end
|
||
|
function p.makeDir(path)
|
||
|
local parent = structure
|
||
|
for part in string.gmatch(path, "[^/]+") do
|
||
|
if not parent.files[part] then
|
||
|
parent.files[part] = {
|
||
|
files = {},
|
||
|
}
|
||
|
end
|
||
|
parent = parent.files[part]
|
||
|
end
|
||
|
end
|
||
|
function p.copy(path, target)
|
||
|
if p.exists(target) then
|
||
|
error("File exists", 1)
|
||
|
end
|
||
|
if not p.exists(path) then
|
||
|
error("File not found", 1)
|
||
|
end
|
||
|
if p.isDir(path) then
|
||
|
fs.recursiveCopy(p, p, path, target)
|
||
|
else
|
||
|
getFile(fs.getDir(target)).files[fs.getName(target)] = {data = getFile(path).data}
|
||
|
end
|
||
|
end
|
||
|
function p.move(path, target)
|
||
|
getFile(fs.getDir(target)).files[fs.getName(target)] = getFile(fs.getDir(path)).files[fs.getName(path)]
|
||
|
p.delete(path)
|
||
|
end
|
||
|
function p.open(path, mode)
|
||
|
local handle = {buffer = "", closed = false}
|
||
|
if mode == "w" or mode == "a" then
|
||
|
p.makeDir(fs.getDir(path))
|
||
|
if not p.exists(path) then
|
||
|
getFile(fs.getDir(path)).files[fs.getName(path)] = {
|
||
|
data = "",
|
||
|
}
|
||
|
end
|
||
|
handle.file = getFile(path)
|
||
|
if handle.file.files then
|
||
|
return
|
||
|
end
|
||
|
if mode == "a" then
|
||
|
handle.buffer = handle.file.data
|
||
|
end
|
||
|
function handle.close()
|
||
|
if handle.closed then return end
|
||
|
handle.closed = true
|
||
|
handle.flush()
|
||
|
end
|
||
|
function handle.flush()
|
||
|
handle.file.data = handle.buffer
|
||
|
if writeCallback then
|
||
|
writeCallback(structure)
|
||
|
end
|
||
|
end
|
||
|
function handle.write(data)
|
||
|
if handle.closed then return end
|
||
|
handle.buffer = handle.buffer .. data
|
||
|
end
|
||
|
function handle.writeLine(data)
|
||
|
handle.write(data .. "\n")
|
||
|
end
|
||
|
return handle
|
||
|
elseif mode == "r" then
|
||
|
if not p.exists(path) or p.isDir(path) then
|
||
|
return
|
||
|
end
|
||
|
handle.file = getFile(path)
|
||
|
handle.buffer = handle.file.data
|
||
|
function handle.readLine()
|
||
|
if handle.closed then
|
||
|
return
|
||
|
end
|
||
|
if #handle.buffer < 1 then
|
||
|
return nil
|
||
|
end
|
||
|
local tmp = ""
|
||
|
while #handle.buffer > 0 do
|
||
|
local chr = handle.buffer:sub(1, 1)
|
||
|
handle.buffer = handle.buffer:sub(2)
|
||
|
if chr == "\n" then
|
||
|
return tmp
|
||
|
else
|
||
|
tmp = tmp .. chr
|
||
|
end
|
||
|
end
|
||
|
return tmp
|
||
|
end
|
||
|
function handle.readAll()
|
||
|
if handle.closed then
|
||
|
return
|
||
|
end
|
||
|
local str = handle.buffer
|
||
|
handle.buffer = ""
|
||
|
return str
|
||
|
end
|
||
|
function handle.close()
|
||
|
handle.closed = true
|
||
|
end
|
||
|
return handle
|
||
|
else
|
||
|
error("Mode not supported", 1)
|
||
|
end
|
||
|
end
|
||
|
function p.getSize(path)
|
||
|
if p.isDir(path) then
|
||
|
return 0
|
||
|
end
|
||
|
return #getFile(path).data
|
||
|
end
|
||
|
return p
|
||
|
end
|
||
|
function fs.fileProxy(path, label)
|
||
|
if not label then
|
||
|
label = fs.getName(path)
|
||
|
end
|
||
|
return fs.tmpfsProxy(aFile.unserializeFile(path) or {}, function(structure)
|
||
|
aFile.serializeFile(path, structure)
|
||
|
end, label)
|
||
|
end
|
||
|
function fs.redirectProxy(parent, dir)
|
||
|
local p = {}
|
||
|
function p.open(path, mode)
|
||
|
return parent.open(fs.combine(dir, fs.cleanPath(path)), mode)
|
||
|
end
|
||
|
for i, v in ipairs({"delete", "getSize", "getFreeSpace", "makeDir", "getDrive", "isReadOnly", "exists", "isDir", "list"}) do
|
||
|
p[v] = function(path)
|
||
|
return parent[v](fs.combine(dir, fs.cleanPath(path)))
|
||
|
end
|
||
|
end
|
||
|
function p.copy(src, dest)
|
||
|
return parent.copy(fs.combine(dir, fs.cleanPath(src)), fs.combine(dir, fs.cleanPath(dest)))
|
||
|
end
|
||
|
function p.move(src, dest)
|
||
|
return parent.move(fs.combine(dir, fs.cleanPath(src)), fs.combine(dir, fs.cleanPath(dest)))
|
||
|
end
|
||
|
return p
|
||
|
end
|
||
|
if not fs.exists("sys") then
|
||
|
fs.makeDir("sys")
|
||
|
end
|
||
|
if not fs.exists("tmp") then
|
||
|
fs.makeDir("tmp")
|
||
|
end
|
||
|
local sysStructure = {
|
||
|
files = {
|
||
|
reboot = {
|
||
|
read = nil,
|
||
|
write = function(data)
|
||
|
if data == "1" or data == "1\n" then
|
||
|
os.reboot()
|
||
|
end
|
||
|
end,
|
||
|
},
|
||
|
shutdown = {
|
||
|
read = nil,
|
||
|
write = function(data)
|
||
|
if data == "1" or data == "1\n" then
|
||
|
os.shutdown()
|
||
|
end
|
||
|
end,
|
||
|
},
|
||
|
label = {
|
||
|
read = function()
|
||
|
return os.getComputerLabel() or ""
|
||
|
end,
|
||
|
write = function(data)
|
||
|
data = data:gsub("\n", "")
|
||
|
if data == "" then
|
||
|
os.setComputerLabel(nil)
|
||
|
else
|
||
|
os.setComputerLabel(data)
|
||
|
end
|
||
|
end,
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
fs.proxy("sys", fs.appfsProxy(sysStructure))
|
||
|
fs.proxy("tmp", fs.tmpfsProxy())
|