/src/luarocks/tools/tar.lua

http://github.com/keplerproject/luarocks · Lua · 174 lines · 147 code · 14 blank · 13 comment · 58 complexity · 9df2f1f9c35332dead8b6fb5e67db0dd MD5 · raw file

  1. --- A pure-Lua implementation of untar (unpacking .tar archives)
  2. local tar = {}
  3. local fs = require("luarocks.fs")
  4. local dir = require("luarocks.dir")
  5. local fun = require("luarocks.fun")
  6. local blocksize = 512
  7. local function get_typeflag(flag)
  8. if flag == "0" or flag == "\0" then return "file"
  9. elseif flag == "1" then return "link"
  10. elseif flag == "2" then return "symlink" -- "reserved" in POSIX, "symlink" in GNU
  11. elseif flag == "3" then return "character"
  12. elseif flag == "4" then return "block"
  13. elseif flag == "5" then return "directory"
  14. elseif flag == "6" then return "fifo"
  15. elseif flag == "7" then return "contiguous" -- "reserved" in POSIX, "contiguous" in GNU
  16. elseif flag == "x" then return "next file"
  17. elseif flag == "g" then return "global extended header"
  18. elseif flag == "L" then return "long name"
  19. elseif flag == "K" then return "long link name"
  20. end
  21. return "unknown"
  22. end
  23. local function octal_to_number(octal)
  24. local exp = 0
  25. local number = 0
  26. octal = octal:gsub("%s", "")
  27. for i = #octal,1,-1 do
  28. local digit = tonumber(octal:sub(i,i))
  29. if not digit then
  30. break
  31. end
  32. number = number + (digit * 8^exp)
  33. exp = exp + 1
  34. end
  35. return number
  36. end
  37. local function checksum_header(block)
  38. local sum = 256
  39. for i = 1,148 do
  40. local b = block:byte(i) or 0
  41. sum = sum + b
  42. end
  43. for i = 157,500 do
  44. local b = block:byte(i) or 0
  45. sum = sum + b
  46. end
  47. return sum
  48. end
  49. local function nullterm(s)
  50. return s:match("^[^%z]*")
  51. end
  52. local function read_header_block(block)
  53. local header = {}
  54. header.name = nullterm(block:sub(1,100))
  55. header.mode = nullterm(block:sub(101,108)):gsub(" ", "")
  56. header.uid = octal_to_number(nullterm(block:sub(109,116)))
  57. header.gid = octal_to_number(nullterm(block:sub(117,124)))
  58. header.size = octal_to_number(nullterm(block:sub(125,136)))
  59. header.mtime = octal_to_number(nullterm(block:sub(137,148)))
  60. header.chksum = octal_to_number(nullterm(block:sub(149,156)))
  61. header.typeflag = get_typeflag(block:sub(157,157))
  62. header.linkname = nullterm(block:sub(158,257))
  63. header.magic = block:sub(258,263)
  64. header.version = block:sub(264,265)
  65. header.uname = nullterm(block:sub(266,297))
  66. header.gname = nullterm(block:sub(298,329))
  67. header.devmajor = octal_to_number(nullterm(block:sub(330,337)))
  68. header.devminor = octal_to_number(nullterm(block:sub(338,345)))
  69. header.prefix = block:sub(346,500)
  70. -- if header.magic ~= "ustar " and header.magic ~= "ustar\0" then
  71. -- return false, ("Invalid header magic %6x"):format(bestring_to_number(header.magic))
  72. -- end
  73. -- if header.version ~= "00" and header.version ~= " \0" then
  74. -- return false, "Unknown version "..header.version
  75. -- end
  76. if not checksum_header(block) == header.chksum then
  77. return false, "Failed header checksum"
  78. end
  79. return header
  80. end
  81. function tar.untar(filename, destdir)
  82. assert(type(filename) == "string")
  83. assert(type(destdir) == "string")
  84. local tar_handle = io.open(filename, "rb")
  85. if not tar_handle then return nil, "Error opening file "..filename end
  86. local long_name, long_link_name
  87. local ok, err
  88. local make_dir = fun.memoize(fs.make_dir)
  89. while true do
  90. local block
  91. repeat
  92. block = tar_handle:read(blocksize)
  93. until (not block) or checksum_header(block) > 256
  94. if not block then break end
  95. if #block < blocksize then
  96. ok, err = nil, "Invalid block size -- corrupted file?"
  97. break
  98. end
  99. local header
  100. header, err = read_header_block(block)
  101. if not header then
  102. ok = false
  103. break
  104. end
  105. local file_data = tar_handle:read(math.ceil(header.size / blocksize) * blocksize):sub(1,header.size)
  106. if header.typeflag == "long name" then
  107. long_name = nullterm(file_data)
  108. elseif header.typeflag == "long link name" then
  109. long_link_name = nullterm(file_data)
  110. else
  111. if long_name then
  112. header.name = long_name
  113. long_name = nil
  114. end
  115. if long_link_name then
  116. header.name = long_link_name
  117. long_link_name = nil
  118. end
  119. end
  120. local pathname = dir.path(destdir, header.name)
  121. pathname = fs.absolute_name(pathname)
  122. if header.typeflag == "directory" then
  123. ok, err = make_dir(pathname)
  124. if not ok then
  125. break
  126. end
  127. elseif header.typeflag == "file" then
  128. local dirname = dir.dir_name(pathname)
  129. if dirname ~= "" then
  130. ok, err = make_dir(dirname)
  131. if not ok then
  132. break
  133. end
  134. end
  135. local file_handle
  136. file_handle, err = io.open(pathname, "wb")
  137. if not file_handle then
  138. ok = nil
  139. break
  140. end
  141. file_handle:write(file_data)
  142. file_handle:close()
  143. fs.set_time(pathname, header.mtime)
  144. if header.mode:match("[75]") then
  145. fs.set_permissions(pathname, "exec", "all")
  146. else
  147. fs.set_permissions(pathname, "read", "all")
  148. end
  149. end
  150. --[[
  151. for k,v in pairs(header) do
  152. util.printout("[\""..tostring(k).."\"] = "..(type(v)=="number" and v or "\""..v:gsub("%z", "\\0").."\""))
  153. end
  154. util.printout()
  155. --]]
  156. end
  157. tar_handle:close()
  158. return ok, err
  159. end
  160. return tar