Lua Tar Extraction Module
From GiderosMobile
A Lua Tar Extraction Module
-- Tar extraction module based off LUARocks : github.com/keplerproject/luarocks
-- By Danny @ anscamobile
--
-- Restored directory creation
-- Handles space-padded numbers, e.g. in the size field, so it can handle a TAR file
-- created by the OS X UNIX tar.
-- By David Gross
-- Adapted for Gideros Mobile by Emanuel Braz - fastencoding@gmail.com
-- test mock
-- delete tar file after unpacking
-- callback function if needed
--local lfs = require "lfs"
local blocksize = 512
local _ceil = math.ceil
local _tonumber = tonumber
local _ioOpen = io.open
--local byte = string.byte
-- trim from http://lua-users.org/wiki/StringTrim
function trim(s)
return s:match'^%s*(.*%S)' or ''
end
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 = trim(octal)
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
-- Checksum
local function checksum_header(block)
local sum = 256
for i = 1,148 do
sum = sum + block:byte(i)
end
for i = 157,500 do
sum = sum + block:byte(i)
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))
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)
header.pad = block:sub(501,512)
if header.magic ~= "ustar " and header.magic ~= "ustar\0" then
return false, "Invalid header magic "..header.magic
end
if header.version ~= "00" and header.version ~= " \0" then
return false, "Unknown version "..header.version
end
if not checksum_header(block) == header.chksum then
return false, "Failed header checksum"
end
return header
end
--[[
Untar file - Descompacta arquivo
param(string) path for tar file
param(string) path for destination folder
param(bool) To delete after unpack
param(function) callbak function
--]]
function untar(filePath, destPath, removeOldFiles)
if filePath == "mock" then return true end
local tar_handle = io.open(filePath, "rb")
if not tar_handle then return nil, "Error opening file" end
local long_name, long_link_name
while true do
local block
-- Read a header
repeat
block = tar_handle:read(blocksize)
until (not block) or checksum_header(block) > 256
if not block then break end
local header, err = read_header_block(block)
if not header then print(err) end
-- read entire file that follows header
local file_data = tar_handle:read(_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
if (destPath and string.sub(destPath,-1) ~= "/") then
pathname = destPath.."/"..header.name
else
pathname = destPath..header.name
end
if header.typeflag == "directory" then
os.execute("mkdir " .. pathname) -- lfs.mkdir( pathname )
elseif header.typeflag == "file" then
local file_handle = io.open(pathname, "wb")
file_handle:write(file_data)
file_handle:close()
end
end
tar_handle:close()
if removeOldFiles == true then
local ok, err = os.remove(filePath)
if not ok then return nil, err end
end
-- if onComplete then
-- if type(onComplete) == "function" then
-- print ("TAR COMPLETED...")
-- onComplete()
-- else
-- print("TAR ERROR : OnComplete must be a function")
-- end
-- end
return true
end
Usage
--[[
Untar file - Descompacta arquivo
param(string) path for tar file
param(string) path for destination folder
param(bool) To delete after unpack
param(function) callbak function
--]]
untar("yourfile.tar","|D|") -- (filePath, destPath, removeOldFiles)