1
0
mirror of https://github.com/AR2000AR/openComputers_codes.git synced 2025-09-06 21:51:14 +02:00
Files
openComputers_codes/tar/lib/tar.lua

367 lines
11 KiB
Lua

local filesystem = require("filesystem")
local shell = require("shell")
local io = require("io")
---@class tarHeader
---@field name any
---@field mode any
---@field uid any
---@field gid any
---@field size any
---@field mtime any
---@field chksum any
---@field typeflag any
---@field linkname any
---@field magic any
---@field version any
---@field uname any
---@field gname any
---@field devmajor any
---@field devminor any
---@field prefix any
---https://github.com/luarocks/luarocks/blob/master/src/luarocks/core/dir.lua
local function unquote(c)
local first, last = c:sub(1, 1), c:sub(-1)
if (first == '"' and last == '"') or
(first == "'" and last == "'") then
return c:sub(2, -2)
end
return c
end
local dir = {}
function dir.path(...)
local t = {...}
while t[1] == "" do
table.remove(t, 1)
end
for i, c in ipairs(t) do
t[i] = unquote(c)
end
return (table.concat(t, "/"):gsub("([^:])/+", "%1/"):gsub("^/+", "/"):gsub("/*$", ""))
end
---https://github.com/luarocks/luarocks/blob/master/src/luarocks/tools/tar.lua#L56
--- A pure-Lua implementation of untar (unpacking .tar archives)
local tar = {}
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 checksum_header(block) ~= header.chksum then
return false, "Failed header checksum"
end
return header
end
function tar.untar(filename, destdir)
checkArg(1, filename, "string")
checkArg(2, destdir, "string")
local tar_handle = io.open(filename, "rb")
if not tar_handle then return nil, "Error opening file " .. filename end
local long_name, long_link_name
local ok, err
local make_dir = filesystem.makeDirectory
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 = dir.path(destdir, header.name)
pathname = filesystem.canonical(pathname)
if header.typeflag == "directory" then
make_dir(pathname)
elseif header.typeflag == "file" then
local dirname = filesystem.path(pathname)
if dirname ~= "" then
make_dir(dirname)
end
local file_handle
file_handle, err = io.open(pathname, "wb")
if not file_handle then
ok = nil
require("event").onError("[tar]" .. err)
break
end
file_handle:write(file_data):close()
end
end
tar_handle:close()
return ok, err
end
--END OF luarocks original code
---Retrun the headers
---@param filename string
---@return table<number,tarHeader>?, string? reason
function tar.list(filename)
checkArg(1, filename, 'string')
local tar_handle = io.open(filename, "rb")
if not tar_handle then return nil, "Error opening file " .. filename end
local ok, err
local long_name, long_link_name
local files = {}
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
if (header.typeflag == "file" or header.typeflag == "directory") then
table.insert(files, header)
end
end
tar_handle:close()
return files, err
end
---Extract files that match filename `fineNameInArchive:match("^"..filename)
---@param tarname string Tar archive to work with
---@param filename? string|table file name patern to extract
---@param overwrite boolean overwrite existing files.
---@param ignore? string|table file name patern to ignore
---@param destdir string
---@param newRoot? string
function tar.extract(tarname, destdir, overwrite, filename, ignore, newRoot)
checkArg(1, tarname, "string")
checkArg(2, destdir, "string")
checkArg(3, overwrite, "boolean")
checkArg(4, filename, "string", "table", 'nil')
checkArg(5, ignore, "string", "table", 'nil')
checkArg(6, destdir, "string")
checkArg(7, newRoot, "string", "nil")
---@param name string
---@return boolean
local function shouldExtractFile(name)
local ok = true
if (filename and type(filename) == "string") then
if (not name:match("^" .. filename)) then ok = false end
end
if (filename and type(filename) == "table") then
for _, fname in pairs(filename) do
if (not name:match("^" .. fname)) then ok = false end
end
end
if (ignore and type(ignore) == "string") then
if (name:match("^" .. ignore)) then ok = false end
end
if (ignore and type(ignore) == "table") then
for _, fname in pairs(ignore) do
if (name:match("^" .. fname)) then ok = false end
end
end
if (name == newRoot) then ok = false end
return ok
end
local tar_handle = io.open(tarname, "rb")
if not tar_handle then return nil, "Error opening file " .. tarname end
local long_name, long_link_name
---@type boolean?, string?
local ok, err = true, nil
local make_dir = filesystem.makeDirectory
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
if (shouldExtractFile(header.name)) then
local pathname = dir.path(destdir, header.name)
if (newRoot) then --change the archive root
pathname = pathname:gsub(newRoot, "")
end
pathname = filesystem.canonical(pathname)
if header.typeflag == "directory" then
if (pathname ~= newRoot) then make_dir(pathname) end
elseif header.typeflag == "file" then
local dirname = filesystem.path(pathname)
if dirname ~= "" then
make_dir(dirname)
end
if (overwrite or not (filesystem.exists(pathname) and not filesystem.isDirectory(pathname))) then
local file_handle
file_handle, err = io.open(pathname, "wb")
if not file_handle then
ok = nil
require("event").onError("[tar]" .. err)
break
end
file_handle:write(file_data):close()
end
end
end
end
tar_handle:close()
return ok, err
end
return tar