view share/lua/5.2/luarocks/tools/tar.lua @ 8065:591b1467ccdf

<int-e> le/rn paste/"Paste" is a short story by Henry James. Its contents has been cut into pieces and distributed over numerous tin boxes on the World Wide Web, little pearls of wisdom buried among ordinary pastes.
author HackBot
date Sun, 15 May 2016 13:14:57 +0000
parents d137f631bad5
children
line wrap: on
line source


--- A pure-Lua implementation of untar (unpacking .tar archives)
module("luarocks.tools.tar", package.seeall)

local fs = require("luarocks.fs")
local dir = require("luarocks.dir")
local util = require("luarocks.util")

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
   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
      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)
   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

function untar(filename, destdir)
   assert(type(filename) == "string")
   assert(type(destdir) == "string")

   local tar_handle = io.open(filename, "r")
   if not tar_handle then return nil, "Error opening file "..filename end
   
   local long_name, long_link_name
   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
      local header, err = read_header_block(block)
      if not header then
         util.printerr(err)
      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)
      if header.typeflag == "directory" then
         fs.make_dir(pathname)
      elseif header.typeflag == "file" then
         local dirname = dir.dir_name(pathname)
         if dirname ~= "" then
            fs.make_dir(dirname)
         end
         local file_handle = io.open(pathname, "wb")
         file_handle:write(file_data)
         file_handle:close()
         fs.set_time(pathname, header.mtime)
         if fs.chmod then
            fs.chmod(pathname, header.mode)
         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
   return true
end