summaryrefslogtreecommitdiff
path: root/src/luarocks/tools/tar.lua
diff options
context:
space:
mode:
Diffstat (limited to 'src/luarocks/tools/tar.lua')
-rw-r--r--src/luarocks/tools/tar.lua191
1 files changed, 191 insertions, 0 deletions
diff --git a/src/luarocks/tools/tar.lua b/src/luarocks/tools/tar.lua
new file mode 100644
index 0000000..bac7b2a
--- /dev/null
+++ b/src/luarocks/tools/tar.lua
@@ -0,0 +1,191 @@
+
+--- A pure-Lua implementation of untar (unpacking .tar archives)
+local tar = {}
+
+local fs = require("luarocks.fs")
+local dir = require("luarocks.dir")
+local fun = require("luarocks.fun")
+
+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
+
+ if block:byte(1) == 0 then
+ return 0
+ end
+
+ 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 header.magic ~= "ustar " and header.magic ~= "ustar\0" then
+ -- return false, ("Invalid header magic %6x"):format(bestring_to_number(header.magic))
+ -- end
+ -- if header.version ~= "00" and header.version ~= " \0" then
+ -- return false, "Unknown version "..header.version
+ -- end
+ if header.typeflag == "unknown" then
+ if checksum_header(block) ~= header.chksum then
+ return false, "Failed header checksum"
+ end
+ end
+ return header
+end
+
+function tar.untar(filename, destdir)
+ 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 long_name, long_link_name
+ local ok, err
+ local make_dir = fun.memoize(fs.make_dir)
+ while true do
+ local block
+ repeat
+ block = tar_handle:read(blocksize)
+ until (not block) or block:byte(1) > 0
+ 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 = ""
+ if header.size > 0 then
+ local nread = math.ceil(header.size / blocksize) * blocksize
+ file_data = tar_handle:read(header.size)
+ if nread > header.size then
+ tar_handle:seek("cur", nread - header.size)
+ end
+ end
+
+ 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 = fs.absolute_name(pathname)
+ if header.typeflag == "directory" then
+ ok, err = make_dir(pathname)
+ if not ok then
+ break
+ end
+ elseif header.typeflag == "file" then
+ local dirname = dir.dir_name(pathname)
+ if dirname ~= "" then
+ ok, err = make_dir(dirname)
+ if not ok then
+ break
+ end
+ end
+ local file_handle
+ file_handle, err = io.open(pathname, "wb")
+ if not file_handle then
+ ok = nil
+ break
+ end
+ file_handle:write(file_data)
+ file_handle:close()
+ fs.set_time(pathname, header.mtime)
+ if header.mode:match("[75]") then
+ fs.set_permissions(pathname, "exec", "all")
+ else
+ fs.set_permissions(pathname, "read", "all")
+ end
+ end
+ --[[
+ for k,v in pairs(header) do
+ util.printout("[\""..tostring(k).."\"] = "..(type(v)=="number" and v or "\""..v:gsub("%z", "\\0").."\""))
+ end
+ util.printout()
+ --]]
+ end
+ tar_handle:close()
+ return ok, err
+end
+
+return tar