Files
HyperionOS/misc/cct/installdata/tarbad

158 lines
3.9 KiB
Plaintext

local function octal_to_number(str)
str = str:gsub("%z", ""):match("^%s*(.-)%s*$")
return tonumber(str, 8) or 0
end
local function dedupe_path(path)
local parts = {}
for p in path:gmatch("[^/]+") do table.insert(parts, p) end
for prefix_len = 1, math.floor(#parts / 2) do
local ok = true
for i = 1, prefix_len do
if parts[i] ~= parts[i + prefix_len] then
ok = false
break
end
end
if ok then
local cleaned = {}
for i = 1, #parts - prefix_len do
cleaned[#cleaned + 1] = parts[i + prefix_len]
end
return table.concat(cleaned, "/")
end
end
return path
end
local function make_dirs(root, path)
local cur = root
for part in path:gmatch("([^/]+)/") do
if not cur[part] then
cur[part] = { __type = "dir", __entries = {} }
end
cur = cur[part].__entries
end
return cur
end
local function flatten(node, prefix)
local out = {}
prefix = prefix or ""
for name, obj in pairs(node) do
local full = prefix .. name
if obj.__type == "file" then
out[#out+1] = {
name = full,
type = "file",
contents = obj.__contents
}
elseif obj.__type == "dir" then
out[#out+1] = {
name = full .. "/",
type = "dir",
contents = flatten(obj.__entries, full .. "/")
}
end
end
return out
end
local function unpack_tar(tarstr)
local i = 1
local len = #tarstr
local root = {}
while i + 512 <= len do
local header = tarstr:sub(i, i + 511)
if header:match("^\0+$") then break end
local name_raw = header:sub(1, 100):gsub("%z.*", "")
local prefix_raw = header:sub(346, 500):gsub("%z.*", "")
local name
if prefix_raw ~= "" then
name = prefix_raw .. "/" .. name_raw
else
name = name_raw
end
name = name:gsub("^%./", ""):gsub("/+", "/")
name = dedupe_path(name)
local size = octal_to_number(header:sub(125,136))
local typeflag = header:sub(157,157)
i = i + 512
local contents = tarstr:sub(i, i + size - 1)
local pad = (512 - (size % 512)) % 512
i = i + size + pad
if name == "" then goto continue end
local is_dir = typeflag == "5" or name:sub(-1) == "/"
local clean_name = name:gsub("/$", "")
if clean_name == "" then goto continue end
local parent_path = clean_name:match("(.+)/")
local fname = clean_name:match("([^/]+)$")
if not fname then goto continue end
local parent = root
if parent_path then
parent = make_dirs(root, parent_path .. "/")
end
if is_dir then
parent[fname] = parent[fname] or { __type = "dir", __entries = {} }
else
parent[fname] = { __type = "file", __contents = contents }
end
::continue::
end
return flatten(root)
end
local function write_directory(prefix, items)
for _, v in ipairs(items) do
if v.type == "dir" then
fs.makeDir(prefix..v.name)
write_directory(prefix, v.contents)
elseif v.type == "file" then
local file = fs.open(prefix..v.name, "w")
file.write(v.contents)
file.close()
end
end
end
local in_tar = ({...})[1]
local out_dir = ({...})[2]
if not in_tar or not out_dir then
print("Usage: unpack_tar <tarfile> <output_dir>")
return
end
local f = fs.open(in_tar, "r")
local tarstr = f.readAll()
f.close()
local list = unpack_tar(tarstr)
write_directory(out_dir, list)
print("TAR extracted into: " .. out_dir)