mirror of https://github.com/kepler155c/opus
271 lines
6.5 KiB
Lua
271 lines
6.5 KiB
Lua
|
|
-- see: https://github.com/luarocks/luarocks/blob/master/src/luarocks/tools/tar.lua
|
|
-- A pure-Lua implementation of untar (unpacking .tar archives)
|
|
local Util = require('opus.util')
|
|
|
|
local fs = _G.fs
|
|
local _sub = string.sub
|
|
|
|
local blocksize = 512
|
|
|
|
local function get_typeflag(flag)
|
|
if flag == "0" or flag == "\0" then return "file"
|
|
elseif flag == "1" then return "link"
|
|
elseif flag == "2" then return "symlink" -- "reserved" in POSIX, "symlink" in GNU
|
|
elseif flag == "3" then return "character"
|
|
elseif flag == "4" then return "block"
|
|
elseif flag == "5" then return "directory"
|
|
elseif flag == "6" then return "fifo"
|
|
elseif flag == "7" then return "contiguous" -- "reserved" in POSIX, "contiguous" in GNU
|
|
elseif flag == "x" then return "next file"
|
|
elseif flag == "g" then return "global extended header"
|
|
elseif flag == "L" then return "long name"
|
|
elseif flag == "K" then return "long link name"
|
|
end
|
|
return "unknown"
|
|
end
|
|
|
|
local function octal_to_number(octal)
|
|
local exp = 0
|
|
local number = 0
|
|
octal = octal:gsub("%s", "")
|
|
for i = #octal,1,-1 do
|
|
local digit = tonumber(octal:sub(i,i))
|
|
if not digit then
|
|
break
|
|
end
|
|
number = number + (digit * 8^exp)
|
|
exp = exp + 1
|
|
end
|
|
return number
|
|
end
|
|
|
|
local function checksum_header(block)
|
|
local sum = 256
|
|
for i = 1,148 do
|
|
local b = block:byte(i) or 0
|
|
sum = sum + b
|
|
end
|
|
for i = 157,500 do
|
|
local b = block:byte(i) or 0
|
|
sum = sum + b
|
|
end
|
|
return sum
|
|
end
|
|
|
|
local function nullterm(s)
|
|
return s:match("^[^%z]*")
|
|
end
|
|
|
|
local function read_header_block(block)
|
|
local header = {}
|
|
header.name = nullterm(block:sub(1,100))
|
|
header.mode = nullterm(block:sub(101,108)):gsub(" ", "")
|
|
header.uid = octal_to_number(nullterm(block:sub(109,116)))
|
|
header.gid = octal_to_number(nullterm(block:sub(117,124)))
|
|
header.size = octal_to_number(nullterm(block:sub(125,136)))
|
|
header.mtime = octal_to_number(nullterm(block:sub(137,148)))
|
|
header.chksum = octal_to_number(nullterm(block:sub(149,156)))
|
|
header.typeflag = get_typeflag(block:sub(157,157))
|
|
header.linkname = nullterm(block:sub(158,257))
|
|
header.magic = block:sub(258,263)
|
|
header.version = block:sub(264,265)
|
|
header.uname = nullterm(block:sub(266,297))
|
|
header.gname = nullterm(block:sub(298,329))
|
|
header.devmajor = octal_to_number(nullterm(block:sub(330,337)))
|
|
header.devminor = octal_to_number(nullterm(block:sub(338,345)))
|
|
header.prefix = block:sub(346,500)
|
|
if not checksum_header(block) == header.chksum then
|
|
return false, "Failed header checksum"
|
|
end
|
|
return header
|
|
end
|
|
|
|
local function untar_stream(tar_handle, destdir, verbose)
|
|
assert(type(destdir) == "string")
|
|
|
|
local long_name, long_link_name
|
|
local ok, err
|
|
|
|
local make_dir = function(a)
|
|
if not fs.exists(a) then
|
|
fs.makeDir(a)
|
|
end
|
|
return true
|
|
end
|
|
|
|
while true do
|
|
local block
|
|
repeat
|
|
block = tar_handle:read(blocksize)
|
|
until (not block) or checksum_header(block) > 256
|
|
if not block then break end
|
|
if #block < blocksize then
|
|
ok, err = nil, "Invalid block size -- corrupted file?"
|
|
break
|
|
end
|
|
local header
|
|
header, err = read_header_block(block)
|
|
if not header then
|
|
ok = false
|
|
break
|
|
end
|
|
|
|
local file_data = tar_handle:read(math.ceil(header.size / blocksize) * blocksize):sub(1,header.size)
|
|
|
|
if header.typeflag == "long name" then
|
|
long_name = nullterm(file_data)
|
|
elseif header.typeflag == "long link name" then
|
|
long_link_name = nullterm(file_data)
|
|
else
|
|
if long_name then
|
|
header.name = long_name
|
|
long_name = nil
|
|
end
|
|
if long_link_name then
|
|
header.name = long_link_name
|
|
long_link_name = nil
|
|
end
|
|
end
|
|
local pathname = fs.combine(destdir, header.name)
|
|
|
|
if header.typeflag == "directory" then
|
|
ok, err = make_dir(pathname)
|
|
if not ok then
|
|
break
|
|
end
|
|
elseif header.typeflag == "file" then
|
|
local dirname = fs.getDir(pathname)
|
|
if dirname ~= "" then
|
|
ok, err = make_dir(dirname)
|
|
if not ok then
|
|
break
|
|
end
|
|
end
|
|
local file_handle
|
|
if verbose then
|
|
print(pathname)
|
|
end
|
|
file_handle, err = io.open(pathname, "wb")
|
|
if not file_handle then
|
|
ok = nil
|
|
break
|
|
end
|
|
file_handle:write(file_data)
|
|
file_handle:close()
|
|
end
|
|
end
|
|
|
|
return ok, err
|
|
end
|
|
|
|
local function untar_string(str, destdir, verbose)
|
|
local ctr = 1
|
|
local len = #str
|
|
local handle = {
|
|
read = function(_, n)
|
|
if ctr < len then
|
|
local s = _sub(str, ctr, ctr + n - 1)
|
|
ctr = ctr + n
|
|
return s
|
|
end
|
|
end
|
|
}
|
|
return untar_stream(handle, destdir, verbose)
|
|
end
|
|
|
|
local function untar(filename, destdir, verbose)
|
|
assert(type(filename) == "string")
|
|
assert(type(destdir) == "string")
|
|
|
|
local tar_handle = io.open(filename, "rb")
|
|
if not tar_handle then return nil, "Error opening file "..filename end
|
|
|
|
local ok, err = untar_stream(filename, destdir, verbose)
|
|
|
|
tar_handle:close()
|
|
return ok, err
|
|
end
|
|
|
|
local function create_header_block(filename, abspath)
|
|
local block = ('\0'):rep(blocksize)
|
|
|
|
local function number_to_octal(n)
|
|
return ('%o'):format(n)
|
|
end
|
|
|
|
local function ins(pos, istr)
|
|
block = block:sub(1, pos - 1) .. istr .. block:sub(pos + #istr)
|
|
end
|
|
|
|
ins(1, filename) -- header
|
|
ins(125, number_to_octal(fs.getSize(abspath)))
|
|
ins(157, '0') -- typeflag
|
|
|
|
ins(149, number_to_octal(checksum_header(block)))
|
|
|
|
return block
|
|
end
|
|
|
|
local function tar_stream(tar_handle, root, files)
|
|
if not files then
|
|
files = { }
|
|
local function recurse(rel)
|
|
local abs = fs.combine(root, rel)
|
|
for _,f in ipairs(fs.list(abs)) do
|
|
local fullName = fs.combine(abs, f)
|
|
if fs.isDir(fullName) then -- skip virtual dirs
|
|
recurse(fs.combine(rel, f))
|
|
else
|
|
table.insert(files, fs.combine(rel, f))
|
|
end
|
|
end
|
|
end
|
|
recurse('')
|
|
end
|
|
|
|
for _, file in pairs(files) do
|
|
local abs = fs.combine(root, file)
|
|
local block = create_header_block(file, abs)
|
|
tar_handle:write(block)
|
|
local f = Util.readFile(abs, 'rb')
|
|
tar_handle:write(f)
|
|
local padding = #f % blocksize
|
|
if padding > 0 then
|
|
tar_handle:write(('\0'):rep(blocksize - padding))
|
|
end
|
|
end
|
|
end
|
|
|
|
local function tar_string(root, files)
|
|
local t = { }
|
|
local handle = {
|
|
write = function(_, s)
|
|
table.insert(t, s)
|
|
end
|
|
}
|
|
tar_stream(handle, root, files)
|
|
return table.concat(t)
|
|
end
|
|
|
|
-- the bare minimum for this program to untar
|
|
local function tar(filename, root, files)
|
|
assert(type(filename) == "string")
|
|
assert(type(root) == "string")
|
|
|
|
local tar_handle = io.open(filename, "wb")
|
|
if not tar_handle then return nil, "Error opening file "..filename end
|
|
|
|
local ok, err = tar_stream(tar_handle, root, files)
|
|
|
|
tar_handle:close()
|
|
return ok, err
|
|
end
|
|
|
|
return {
|
|
tar = tar,
|
|
untar = untar,
|
|
tar_string = tar_string,
|
|
untar_string = untar_string,
|
|
}
|