diff options
Diffstat (limited to 'src/luarocks/tools/tar.lua')
| -rw-r--r-- | src/luarocks/tools/tar.lua | 191 |
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 |
