local M = {}
local function shell_quote(s)
return "'" .. tostring(s):gsub("'", [['\'']]) .. "'"
end
local function sudo_prefix()
local r = shell.exec("id -u", {})
if r and r.status == 0 and (r.stdout or ""):match("^0%s*$") then return "" end
return "sudo -n "
end
function M.detect(path)
local r = shell.exec(("findmnt -nT %s -o SOURCE,FSTYPE"):format(shell_quote(path)), {})
if not r or r.status ~= 0 then
return { backend = "none", source = "", fstype = "" }
end
local source, fstype = (r.stdout or ""):match("^(%S+)%s+(%S+)")
source = source or ""
fstype = (fstype or ""):lower()
local backend = "none"
if fstype == "btrfs" then backend = "btrfs"
elseif fstype == "zfs" then backend = "zfs"
elseif fstype:match("^zfs") then backend = "zfs"
end
return { backend = backend, source = source, fstype = fstype }
end
function M.take(name, path)
local info = M.detect(path)
local sudo = sudo_prefix()
local stamp = tostring(os.time())
local snap_id = name .. "-" .. stamp
if info.backend == "btrfs" then
local snap_path = path:gsub("/+$", "") .. "/.assay-snap-" .. snap_id
local cmd = sudo ..
("btrfs subvolume snapshot -r %s %s"):format(shell_quote(path), shell_quote(snap_path))
local r = shell.exec(cmd, { timeout = 30 })
if not r or r.status ~= 0 then
error("fs_snapshot.take(btrfs): " .. ((r and r.stderr) or "unknown"))
end
return { backend = "btrfs", path = snap_path, source_path = path }
end
if info.backend == "zfs" then
local snap_ref = info.source .. "@" .. snap_id
local cmd = sudo .. ("zfs snapshot %s"):format(shell_quote(snap_ref))
local r = shell.exec(cmd, { timeout = 30 })
if not r or r.status ~= 0 then
error("fs_snapshot.take(zfs): " .. ((r and r.stderr) or "unknown"))
end
return {
backend = "zfs",
snap_ref = snap_ref,
source_path = path,
path = path:gsub("/+$", "") .. "/.zfs/snapshot/" .. snap_id,
}
end
return { backend = "none", path = path, source_path = path }
end
function M.release(handle)
if not handle or handle.backend == "none" then return { ok = true } end
local sudo = sudo_prefix()
if handle.backend == "btrfs" then
local cmd = sudo ..
("btrfs subvolume delete %s"):format(shell_quote(handle.path))
local r = shell.exec(cmd, { timeout = 30 })
if not r or r.status ~= 0 then
return { ok = false, error = (r and r.stderr) or "unknown" }
end
return { ok = true }
end
if handle.backend == "zfs" then
local cmd = sudo .. ("zfs destroy %s"):format(shell_quote(handle.snap_ref))
local r = shell.exec(cmd, { timeout = 30 })
if not r or r.status ~= 0 then
return { ok = false, error = (r and r.stderr) or "unknown" }
end
return { ok = true }
end
return { ok = false, error = "unknown backend: " .. tostring(handle.backend) }
end
function M.with_snapshot(name, path, fn)
local handle = M.take(name, path)
local ok, ret = pcall(fn, handle)
M.release(handle)
if not ok then error(ret) end
return ret
end
return M