diff options
| author | Hisham Muhammad <hisham@gobolinux.org> | 2018-07-13 23:00:07 -0300 |
|---|---|---|
| committer | Hisham Muhammad <hisham@gobolinux.org> | 2018-07-18 11:24:07 -0300 |
| commit | f4f1adb8f302ce986c2ed9d65934da51f41dd606 (patch) | |
| tree | c1a7a2755c39acccb2816a5043a8200fb8e2ec74 | |
| parent | 94b25d8922a2bae1c6d5e36bf6b0cffd86d91f2a (diff) | |
| download | luarocks-f4f1adb8f302ce986c2ed9d65934da51f41dd606.tar.gz luarocks-f4f1adb8f302ce986c2ed9d65934da51f41dd606.tar.bz2 luarocks-f4f1adb8f302ce986c2ed9d65934da51f41dd606.zip | |
fs: make unpack_archive platform-agnostic using specific fs functions
Use luarocks.tools.tar for handling tar files, and add platform-specific
functions fs.zip, fs.unzip, fs.bunzip2, fs.gunzip, giving them native
implementations using Lua modules or alternative implementations using
third-party tools.
| -rwxr-xr-x | binary/all_in_one | 1 | ||||
| -rw-r--r-- | spec/fixtures/abc.bz2 | bin | 0 -> 66 bytes | |||
| -rw-r--r-- | spec/fs_spec.lua | 16 | ||||
| -rw-r--r-- | src/luarocks/fs/lua.lua | 148 | ||||
| -rw-r--r-- | src/luarocks/fs/unix/tools.lua | 60 | ||||
| -rw-r--r-- | src/luarocks/fs/win32/tools.lua | 102 | ||||
| -rw-r--r-- | src/luarocks/tools/zip.lua | 368 |
7 files changed, 504 insertions, 191 deletions
diff --git a/binary/all_in_one b/binary/all_in_one index 882289f5..dcef0fbf 100755 --- a/binary/all_in_one +++ b/binary/all_in_one | |||
| @@ -463,7 +463,6 @@ local function main() | |||
| 463 | 463 | ||
| 464 | local dependencies = { | 464 | local dependencies = { |
| 465 | md5 = "md5", | 465 | md5 = "md5", |
| 466 | luazip = if_platform("unix", "luazip"), | ||
| 467 | luasec = "./binary/luasec-0.7alpha-2.rockspec", | 466 | luasec = "./binary/luasec-0.7alpha-2.rockspec", |
| 468 | luaposix = if_platform("unix", "./binary/luaposix-34.0.4-1.rockspec"), | 467 | luaposix = if_platform("unix", "./binary/luaposix-34.0.4-1.rockspec"), |
| 469 | luasocket = "luasocket", | 468 | luasocket = "luasocket", |
diff --git a/spec/fixtures/abc.bz2 b/spec/fixtures/abc.bz2 new file mode 100644 index 00000000..ee786715 --- /dev/null +++ b/spec/fixtures/abc.bz2 | |||
| Binary files differ | |||
diff --git a/spec/fs_spec.lua b/spec/fs_spec.lua index 66453404..5bec6168 100644 --- a/spec/fs_spec.lua +++ b/spec/fs_spec.lua | |||
| @@ -1293,6 +1293,22 @@ describe("Luarocks fs test #unit", function() | |||
| 1293 | assert.falsy(exists_file("nonexistent")) | 1293 | assert.falsy(exists_file("nonexistent")) |
| 1294 | end) | 1294 | end) |
| 1295 | end) | 1295 | end) |
| 1296 | |||
| 1297 | describe("fs.bunzip2", function() | ||
| 1298 | |||
| 1299 | it("uncompresses a .bz2 file", function() | ||
| 1300 | local input = testing_paths.fixtures_dir .. "/abc.bz2" | ||
| 1301 | local output = os.tmpname() | ||
| 1302 | assert.truthy(fs.bunzip2(input, output)) | ||
| 1303 | local fd = io.open(output, "r") | ||
| 1304 | local content = fd:read("*a") | ||
| 1305 | fd:close() | ||
| 1306 | assert.same(300000, #content) | ||
| 1307 | local abc = ("a"):rep(100000)..("b"):rep(100000)..("c"):rep(100000) | ||
| 1308 | assert.same(abc, content) | ||
| 1309 | end) | ||
| 1310 | |||
| 1311 | end) | ||
| 1296 | 1312 | ||
| 1297 | describe("fs.unzip", function() | 1313 | describe("fs.unzip", function() |
| 1298 | local tmpdir | 1314 | local tmpdir |
diff --git a/src/luarocks/fs/lua.lua b/src/luarocks/fs/lua.lua index f2fa50b6..375cdee3 100644 --- a/src/luarocks/fs/lua.lua +++ b/src/luarocks/fs/lua.lua | |||
| @@ -1,6 +1,6 @@ | |||
| 1 | 1 | ||
| 2 | --- Native Lua implementation of filesystem and platform abstractions, | 2 | --- Native Lua implementation of filesystem and platform abstractions, |
| 3 | -- using LuaFileSystem, LZLib, MD5 and LuaCurl. | 3 | -- using LuaFileSystem, LuaSocket, LuaSec, lua-zlib, LuaPosix, MD5. |
| 4 | -- module("luarocks.fs.lua") | 4 | -- module("luarocks.fs.lua") |
| 5 | local fs_lua = {} | 5 | local fs_lua = {} |
| 6 | 6 | ||
| @@ -10,20 +10,21 @@ local cfg = require("luarocks.core.cfg") | |||
| 10 | local dir = require("luarocks.dir") | 10 | local dir = require("luarocks.dir") |
| 11 | local util = require("luarocks.util") | 11 | local util = require("luarocks.util") |
| 12 | 12 | ||
| 13 | local socket_ok, zip_ok, unzip_ok, lfs_ok, md5_ok, posix_ok, _ | 13 | local socket_ok, zip_ok, lfs_ok, md5_ok, posix_ok, bz2_ok, _ |
| 14 | local http, ftp, lrzip, luazip, lfs, md5, posix | 14 | local http, ftp, zip, lfs, md5, posix, bz2 |
| 15 | 15 | ||
| 16 | if cfg.fs_use_modules then | 16 | if cfg.fs_use_modules then |
| 17 | socket_ok, http = pcall(require, "socket.http") | 17 | socket_ok, http = pcall(require, "socket.http") |
| 18 | _, ftp = pcall(require, "socket.ftp") | 18 | _, ftp = pcall(require, "socket.ftp") |
| 19 | zip_ok, lrzip = pcall(require, "luarocks.tools.zip") | 19 | zip_ok, zip = pcall(require, "luarocks.tools.zip") |
| 20 | unzip_ok, luazip = pcall(require, "zip"); _G.zip = nil | 20 | bz2_ok, bz2 = pcall(require, "luarocks.tools.bzip2") |
| 21 | lfs_ok, lfs = pcall(require, "lfs") | 21 | lfs_ok, lfs = pcall(require, "lfs") |
| 22 | md5_ok, md5 = pcall(require, "md5") | 22 | md5_ok, md5 = pcall(require, "md5") |
| 23 | posix_ok, posix = pcall(require, "posix") | 23 | posix_ok, posix = pcall(require, "posix") |
| 24 | end | 24 | end |
| 25 | 25 | ||
| 26 | local patch = require("luarocks.tools.patch") | 26 | local patch = require("luarocks.tools.patch") |
| 27 | local tar = require("luarocks.tools.tar") | ||
| 27 | 28 | ||
| 28 | local dir_stack = {} | 29 | local dir_stack = {} |
| 29 | 30 | ||
| @@ -589,52 +590,54 @@ end | |||
| 589 | end | 590 | end |
| 590 | 591 | ||
| 591 | --------------------------------------------------------------------- | 592 | --------------------------------------------------------------------- |
| 592 | -- LuaZip functions | 593 | -- lua-bz2 functions |
| 594 | --------------------------------------------------------------------- | ||
| 595 | |||
| 596 | if bz2_ok then | ||
| 597 | |||
| 598 | local function bunzip2_string(data) | ||
| 599 | local decompressor = bz2.initDecompress() | ||
| 600 | local output, err = decompressor:update(data) | ||
| 601 | if not output then | ||
| 602 | return nil, err | ||
| 603 | end | ||
| 604 | decompressor:close() | ||
| 605 | return output | ||
| 606 | end | ||
| 607 | |||
| 608 | --- Uncompresses a .bz2 file. | ||
| 609 | -- @param infile string: pathname of .bz2 file to be extracted. | ||
| 610 | -- @param outfile string or nil: pathname of output file to be produced. | ||
| 611 | -- If not given, name is derived from input file. | ||
| 612 | -- @return boolean: true on success; nil and error message on failure. | ||
| 613 | function fs_lua.bunzip2(infile, outfile) | ||
| 614 | assert(type(infile) == "string") | ||
| 615 | assert(outfile == nil or type(outfile) == "string") | ||
| 616 | if not outfile then | ||
| 617 | outfile = infile:gsub("%.bz2$", "") | ||
| 618 | end | ||
| 619 | |||
| 620 | return fs.filter_file(bunzip2_string, infile, outfile) | ||
| 621 | end | ||
| 622 | |||
| 623 | end | ||
| 624 | |||
| 625 | --------------------------------------------------------------------- | ||
| 626 | -- luarocks.tools.zip functions | ||
| 593 | --------------------------------------------------------------------- | 627 | --------------------------------------------------------------------- |
| 594 | 628 | ||
| 595 | if zip_ok then | 629 | if zip_ok then |
| 596 | 630 | ||
| 597 | function fs_lua.zip(zipfile, ...) | 631 | function fs_lua.zip(zipfile, ...) |
| 598 | return lrzip.zip(zipfile, ...) | 632 | return zip.zip(zipfile, ...) |
| 599 | end | 633 | end |
| 600 | 634 | ||
| 635 | function fs_lua.unzip(zipfile) | ||
| 636 | return zip.unzip(zipfile) | ||
| 601 | end | 637 | end |
| 602 | 638 | ||
| 603 | if unzip_ok then | 639 | function fs_lua.gunzip(infile, outfile) |
| 604 | --- Uncompress files from a .zip archive. | 640 | return zip.gunzip(infile, outfile) |
| 605 | -- @param filename string: pathname of .zip archive to be extracted. | ||
| 606 | -- @return boolean: true on success, false on failure. | ||
| 607 | function fs_lua.unzip(filename) | ||
| 608 | local zipfile, err = luazip.open(filename) | ||
| 609 | if not zipfile then return nil, err end | ||
| 610 | local files = zipfile:files() | ||
| 611 | local file = files() | ||
| 612 | repeat | ||
| 613 | if file.filename:sub(#file.filename) == "/" then | ||
| 614 | local ok, err = fs.make_dir(dir.path(fs.current_dir(), file.filename)) | ||
| 615 | if not ok then return nil, err end | ||
| 616 | else | ||
| 617 | local base = dir.dir_name(file.filename) | ||
| 618 | if base ~= "" then | ||
| 619 | base = dir.path(fs.current_dir(), base) | ||
| 620 | if not fs.is_dir(base) then | ||
| 621 | local ok, err = fs.make_dir(base) | ||
| 622 | if not ok then return nil, err end | ||
| 623 | end | ||
| 624 | end | ||
| 625 | local rf, err = zipfile:open(file.filename) | ||
| 626 | if not rf then zipfile:close(); return nil, err end | ||
| 627 | local contents = rf:read("*a") | ||
| 628 | rf:close() | ||
| 629 | local wf, err = io.open(dir.path(fs.current_dir(), file.filename), "wb") | ||
| 630 | if not wf then zipfile:close(); return nil, err end | ||
| 631 | wf:write(contents) | ||
| 632 | wf:close() | ||
| 633 | end | ||
| 634 | file = files() | ||
| 635 | until not file | ||
| 636 | zipfile:close() | ||
| 637 | return true | ||
| 638 | end | 641 | end |
| 639 | 642 | ||
| 640 | end | 643 | end |
| @@ -878,6 +881,16 @@ local octal_to_rwx = { | |||
| 878 | ["7"] = "rwx", | 881 | ["7"] = "rwx", |
| 879 | } | 882 | } |
| 880 | 883 | ||
| 884 | function fs_lua._unix_rwx_to_number(rwx) | ||
| 885 | local num = 0 | ||
| 886 | for i = 1, 9 do | ||
| 887 | if rwx:sub(10 - i, 10 - i) == "-" then | ||
| 888 | num = num + 2^i | ||
| 889 | end | ||
| 890 | end | ||
| 891 | return num | ||
| 892 | end | ||
| 893 | |||
| 881 | do | 894 | do |
| 882 | local umask_cache | 895 | local umask_cache |
| 883 | function fs_lua._unix_umask() | 896 | function fs_lua._unix_umask() |
| @@ -886,13 +899,8 @@ do | |||
| 886 | end | 899 | end |
| 887 | -- LuaPosix (as of 34.0.4) only returns the umask as rwx | 900 | -- LuaPosix (as of 34.0.4) only returns the umask as rwx |
| 888 | local rwx = posix.umask() | 901 | local rwx = posix.umask() |
| 889 | local oct = 0 | 902 | local num = fs_lua._unix_rwx_to_number(rwx) |
| 890 | for i = 1, 9 do | 903 | umask_cache = ("%03o"):format(num) |
| 891 | if rwx:sub(10 - i, 10 - i) == "-" then | ||
| 892 | oct = oct + 2^i | ||
| 893 | end | ||
| 894 | end | ||
| 895 | umask_cache = ("%03o"):format(oct) | ||
| 896 | return umask_cache | 904 | return umask_cache |
| 897 | end | 905 | end |
| 898 | end | 906 | end |
| @@ -1070,4 +1078,46 @@ function fs_lua.is_lua(filename) | |||
| 1070 | return (result == true) | 1078 | return (result == true) |
| 1071 | end | 1079 | end |
| 1072 | 1080 | ||
| 1081 | --- Unpack an archive. | ||
| 1082 | -- Extract the contents of an archive, detecting its format by | ||
| 1083 | -- filename extension. | ||
| 1084 | -- @param archive string: Filename of archive. | ||
| 1085 | -- @return boolean or (boolean, string): true on success, false and an error message on failure. | ||
| 1086 | function fs_lua.unpack_archive(archive) | ||
| 1087 | assert(type(archive) == "string") | ||
| 1088 | |||
| 1089 | local ok, err | ||
| 1090 | archive = fs.absolute_name(archive) | ||
| 1091 | if archive:match("%.tar%.gz$") then | ||
| 1092 | local tar_filename = archive:gsub("%.gz$", "") | ||
| 1093 | ok, err = fs.gunzip(archive, tar_filename) | ||
| 1094 | if ok then | ||
| 1095 | ok, err = tar.untar(tar_filename, ".") | ||
| 1096 | end | ||
| 1097 | elseif archive:match("%.tgz$") then | ||
| 1098 | local tar_filename = archive:gsub("%.tgz$", ".tar") | ||
| 1099 | ok, err = fs.gunzip(archive, tar_filename) | ||
| 1100 | if ok then | ||
| 1101 | ok, err = tar.untar(tar_filename, ".") | ||
| 1102 | end | ||
| 1103 | elseif archive:match("%.tar%.bz2$") then | ||
| 1104 | local tar_filename = archive:gsub("%.bz2$", "") | ||
| 1105 | ok, err = fs.bunzip2(archive, tar_filename) | ||
| 1106 | if ok then | ||
| 1107 | ok, err = tar.untar(tar_filename, ".") | ||
| 1108 | end | ||
| 1109 | elseif archive:match("%.zip$") then | ||
| 1110 | ok, err = fs.unzip(archive) | ||
| 1111 | elseif archive:match("%.lua$") or archive:match("%.c$") then | ||
| 1112 | -- Ignore .lua and .c files; they don't need to be extracted. | ||
| 1113 | return true | ||
| 1114 | else | ||
| 1115 | return false, "Couldn't extract archive "..archive..": unrecognized filename extension" | ||
| 1116 | end | ||
| 1117 | if not ok then | ||
| 1118 | return false, "Failed extracting "..archive..": "..err | ||
| 1119 | end | ||
| 1120 | return true | ||
| 1121 | end | ||
| 1122 | |||
| 1073 | return fs_lua | 1123 | return fs_lua |
diff --git a/src/luarocks/fs/unix/tools.lua b/src/luarocks/fs/unix/tools.lua index de63eb81..d6277119 100644 --- a/src/luarocks/fs/unix/tools.lua +++ b/src/luarocks/fs/unix/tools.lua | |||
| @@ -136,6 +136,33 @@ function tools.unzip(zipfile) | |||
| 136 | return fs.execute_quiet(vars.UNZIP, zipfile) | 136 | return fs.execute_quiet(vars.UNZIP, zipfile) |
| 137 | end | 137 | end |
| 138 | 138 | ||
| 139 | local function uncompress(default_ext, program, infile, outfile) | ||
| 140 | assert(type(infile) == "string") | ||
| 141 | assert(outfile == nil or type(outfile) == "string") | ||
| 142 | if not outfile then | ||
| 143 | outfile = infile:gsub("%."..default_ext.."$", "") | ||
| 144 | end | ||
| 145 | return fs.execute(fs.Q(program).." -c "..fs.Q(infile).." > "..fs.Q(outfile)) | ||
| 146 | end | ||
| 147 | |||
| 148 | --- Uncompresses a .gz file. | ||
| 149 | -- @param infile string: pathname of .gz file to be extracted. | ||
| 150 | -- @param outfile string or nil: pathname of output file to be produced. | ||
| 151 | -- If not given, name is derived from input file. | ||
| 152 | -- @return boolean: true on success; nil and error message on failure. | ||
| 153 | function tools.gunzip(infile, outfile) | ||
| 154 | return uncompress("gz", "gunzip", infile, outfile) | ||
| 155 | end | ||
| 156 | |||
| 157 | --- Uncompresses a .bz2 file. | ||
| 158 | -- @param infile string: pathname of .bz2 file to be extracted. | ||
| 159 | -- @param outfile string or nil: pathname of output file to be produced. | ||
| 160 | -- If not given, name is derived from input file. | ||
| 161 | -- @return boolean: true on success; nil and error message on failure. | ||
| 162 | function tools.bunzip2(infile, outfile) | ||
| 163 | return uncompress("bz2", "bunzip2", infile, outfile) | ||
| 164 | end | ||
| 165 | |||
| 139 | --- Test is file/directory exists | 166 | --- Test is file/directory exists |
| 140 | -- @param file string: filename to test | 167 | -- @param file string: filename to test |
| 141 | -- @return boolean: true if file exists, false otherwise. | 168 | -- @return boolean: true if file exists, false otherwise. |
| @@ -198,39 +225,6 @@ function tools.set_permissions(filename, mode, scope) | |||
| 198 | return fs.execute(vars.CHMOD, perms, filename) | 225 | return fs.execute(vars.CHMOD, perms, filename) |
| 199 | end | 226 | end |
| 200 | 227 | ||
| 201 | --- Unpack an archive. | ||
| 202 | -- Extract the contents of an archive, detecting its format by | ||
| 203 | -- filename extension. | ||
| 204 | -- @param archive string: Filename of archive. | ||
| 205 | -- @return boolean or (boolean, string): true on success, false and an error message on failure. | ||
| 206 | function tools.unpack_archive(archive) | ||
| 207 | assert(type(archive) == "string") | ||
| 208 | |||
| 209 | local pipe_to_tar = " | "..vars.TAR.." -xf -" | ||
| 210 | |||
| 211 | if not cfg.verbose then | ||
| 212 | pipe_to_tar = " 2> /dev/null"..fs.quiet(pipe_to_tar) | ||
| 213 | end | ||
| 214 | |||
| 215 | local ok | ||
| 216 | if archive:match("%.tar%.gz$") or archive:match("%.tgz$") then | ||
| 217 | ok = fs.execute_string(vars.GUNZIP.." -c "..fs.Q(archive)..pipe_to_tar) | ||
| 218 | elseif archive:match("%.tar%.bz2$") then | ||
| 219 | ok = fs.execute_string(vars.BUNZIP2.." -c "..fs.Q(archive)..pipe_to_tar) | ||
| 220 | elseif archive:match("%.zip$") then | ||
| 221 | ok = fs.execute_quiet(vars.UNZIP, archive) | ||
| 222 | elseif archive:match("%.lua$") or archive:match("%.c$") then | ||
| 223 | -- Ignore .lua and .c files; they don't need to be extracted. | ||
| 224 | return true | ||
| 225 | else | ||
| 226 | return false, "Couldn't extract archive "..archive..": unrecognized filename extension" | ||
| 227 | end | ||
| 228 | if not ok then | ||
| 229 | return false, "Failed extracting "..archive | ||
| 230 | end | ||
| 231 | return true | ||
| 232 | end | ||
| 233 | |||
| 234 | function tools.attributes(filename, attrtype) | 228 | function tools.attributes(filename, attrtype) |
| 235 | local flag = ((attrtype == "permissions") and vars.STATPERMFLAG) | 229 | local flag = ((attrtype == "permissions") and vars.STATPERMFLAG) |
| 236 | or ((attrtype == "owner") and vars.STATOWNERFLAG) | 230 | or ((attrtype == "owner") and vars.STATOWNERFLAG) |
diff --git a/src/luarocks/fs/win32/tools.lua b/src/luarocks/fs/win32/tools.lua index 0a27cbe3..633aae5e 100644 --- a/src/luarocks/fs/win32/tools.lua +++ b/src/luarocks/fs/win32/tools.lua | |||
| @@ -148,6 +148,51 @@ function tools.unzip(zipfile) | |||
| 148 | return fs.execute_quiet(fs.Q(vars.SEVENZ).." -aoa x", zipfile) | 148 | return fs.execute_quiet(fs.Q(vars.SEVENZ).." -aoa x", zipfile) |
| 149 | end | 149 | end |
| 150 | 150 | ||
| 151 | local function sevenz(default_ext, infile, outfile) | ||
| 152 | assert(type(infile) == "string") | ||
| 153 | assert(outfile == nil or type(outfile) == "string") | ||
| 154 | |||
| 155 | local dropext = infile:gsub("%."..default_ext.."$", "") | ||
| 156 | local outdir = dir.dir_name(dropext) | ||
| 157 | |||
| 158 | infile = fs.absolute_name(infile) | ||
| 159 | |||
| 160 | local cmdline = fs.Q(vars.SEVENZ).." -aoa -t* -o"..fs.Q(outdir).." x "..fs.Q(infile) | ||
| 161 | local ok, err = fs.execute_quiet(cmdline) | ||
| 162 | if not ok then | ||
| 163 | return nil, err | ||
| 164 | end | ||
| 165 | |||
| 166 | if outfile then | ||
| 167 | outfile = fs.absolute_name(outfile) | ||
| 168 | dropext = fs.absolute_name(dropext) | ||
| 169 | ok, err = os.rename(dropext, outfile) | ||
| 170 | if not ok then | ||
| 171 | return nil, err | ||
| 172 | end | ||
| 173 | end | ||
| 174 | |||
| 175 | return true | ||
| 176 | end | ||
| 177 | |||
| 178 | --- Uncompresses a .gz file. | ||
| 179 | -- @param infile string: pathname of .gz file to be extracted. | ||
| 180 | -- @param outfile string or nil: pathname of output file to be produced. | ||
| 181 | -- If not given, name is derived from input file. | ||
| 182 | -- @return boolean: true on success; nil and error message on failure. | ||
| 183 | function tools.gunzip(infile, outfile) | ||
| 184 | return sevenz("gz", infile, outfile) | ||
| 185 | end | ||
| 186 | |||
| 187 | --- Uncompresses a .bz2 file. | ||
| 188 | -- @param infile string: pathname of .bz2 file to be extracted. | ||
| 189 | -- @param outfile string or nil: pathname of output file to be produced. | ||
| 190 | -- If not given, name is derived from input file. | ||
| 191 | -- @return boolean: true on success; nil and error message on failure. | ||
| 192 | function tools.bunzip2(infile, outfile) | ||
| 193 | return sevenz("bz2", infile, outfile) | ||
| 194 | end | ||
| 195 | |||
| 151 | --- Test is pathname is a directory. | 196 | --- Test is pathname is a directory. |
| 152 | -- @param file string: pathname to test | 197 | -- @param file string: pathname to test |
| 153 | -- @return boolean: true if it is a directory, false otherwise. | 198 | -- @return boolean: true if it is a directory, false otherwise. |
| @@ -241,63 +286,6 @@ function tools.set_permissions(filename, mode, scope) | |||
| 241 | return true | 286 | return true |
| 242 | end | 287 | end |
| 243 | 288 | ||
| 244 | |||
| 245 | --- Strip the last extension of a filename. | ||
| 246 | -- Example: "foo.tar.gz" becomes "foo.tar". | ||
| 247 | -- If filename has no dots, returns it unchanged. | ||
| 248 | -- @param filename string: The file name to strip. | ||
| 249 | -- @return string: The stripped name. | ||
| 250 | local function strip_extension(filename) | ||
| 251 | assert(type(filename) == "string") | ||
| 252 | return (filename:gsub("%.[^.]+$", "")) or filename | ||
| 253 | end | ||
| 254 | |||
| 255 | --- Uncompress gzip file. | ||
| 256 | -- @param archive string: Filename of archive. | ||
| 257 | -- @return boolean : success status | ||
| 258 | local function gunzip(archive) | ||
| 259 | return fs.execute_quiet(fs.Q(vars.SEVENZ).." -aoa x", archive) | ||
| 260 | end | ||
| 261 | |||
| 262 | --- Unpack an archive. | ||
| 263 | -- Extract the contents of an archive, detecting its format by | ||
| 264 | -- filename extension. | ||
| 265 | -- @param archive string: Filename of archive. | ||
| 266 | -- @return boolean or (boolean, string): true on success, false and an error message on failure. | ||
| 267 | function tools.unpack_archive(archive) | ||
| 268 | assert(type(archive) == "string") | ||
| 269 | |||
| 270 | local ok | ||
| 271 | local sevenzx = fs.Q(vars.SEVENZ).." -aoa x" | ||
| 272 | if archive:match("%.tar%.gz$") then | ||
| 273 | ok = gunzip(archive) | ||
| 274 | if ok then | ||
| 275 | ok = fs.execute_quiet(sevenzx, strip_extension(archive)) | ||
| 276 | end | ||
| 277 | elseif archive:match("%.tgz$") then | ||
| 278 | ok = gunzip(archive) | ||
| 279 | if ok then | ||
| 280 | ok = fs.execute_quiet(sevenzx, strip_extension(archive)..".tar") | ||
| 281 | end | ||
| 282 | elseif archive:match("%.tar%.bz2$") then | ||
| 283 | ok = fs.execute_quiet(sevenzx, archive) | ||
| 284 | if ok then | ||
| 285 | ok = fs.execute_quiet(sevenzx, strip_extension(archive)) | ||
| 286 | end | ||
| 287 | elseif archive:match("%.zip$") then | ||
| 288 | ok = fs.execute_quiet(sevenzx, archive) | ||
| 289 | elseif archive:match("%.lua$") or archive:match("%.c$") then | ||
| 290 | -- Ignore .lua and .c files; they don't need to be extracted. | ||
| 291 | return true | ||
| 292 | else | ||
| 293 | return false, "Couldn't extract archive "..archive..": unrecognized filename extension" | ||
| 294 | end | ||
| 295 | if not ok then | ||
| 296 | return false, "Failed extracting "..archive | ||
| 297 | end | ||
| 298 | return true | ||
| 299 | end | ||
| 300 | |||
| 301 | --- Test for existance of a file. | 289 | --- Test for existance of a file. |
| 302 | -- @param file string: filename to test | 290 | -- @param file string: filename to test |
| 303 | -- @return boolean: true if file exists, false otherwise. | 291 | -- @return boolean: true if file exists, false otherwise. |
diff --git a/src/luarocks/tools/zip.lua b/src/luarocks/tools/zip.lua index e6d9e36a..5974c7bf 100644 --- a/src/luarocks/tools/zip.lua +++ b/src/luarocks/tools/zip.lua | |||
| @@ -1,26 +1,58 @@ | |||
| 1 | 1 | ||
| 2 | --- A Lua implementation of .zip file archiving (used for creating .rock files), | 2 | --- A Lua implementation of .zip and .gz file compression and decompression, |
| 3 | -- using only lzlib or lua-lzib. | 3 | -- using only lzlib or lua-lzib. |
| 4 | local zip = {} | 4 | local zip = {} |
| 5 | 5 | ||
| 6 | local zlib = require("zlib") | 6 | local zlib = require("zlib") |
| 7 | local fs = require("luarocks.fs") | 7 | local fs = require("luarocks.fs") |
| 8 | local fun = require("luarocks.fun") | ||
| 8 | local dir = require("luarocks.dir") | 9 | local dir = require("luarocks.dir") |
| 9 | 10 | ||
| 11 | local stat_ok, stat = pcall(require, "posix.sys.stat") | ||
| 12 | |||
| 13 | local function shr(n, m) | ||
| 14 | return math.floor(n / 2^m) | ||
| 15 | end | ||
| 16 | |||
| 17 | local function shl(n, m) | ||
| 18 | return n * 2^m | ||
| 19 | end | ||
| 20 | local function lowbits(n, m) | ||
| 21 | return n % 2^m | ||
| 22 | end | ||
| 23 | |||
| 24 | local function mode_to_windowbits(mode) | ||
| 25 | if mode == "gzip" then | ||
| 26 | return 31 | ||
| 27 | elseif mode == "zlib" then | ||
| 28 | return 0 | ||
| 29 | elseif mode == "raw" then | ||
| 30 | return -15 | ||
| 31 | end | ||
| 32 | end | ||
| 33 | |||
| 10 | -- zlib module can be provided by both lzlib and lua-lzib packages. | 34 | -- zlib module can be provided by both lzlib and lua-lzib packages. |
| 11 | -- Create a compatibility layer. | 35 | -- Create a compatibility layer. |
| 12 | local zlib_compress, zlib_crc32 | 36 | local zlib_compress, zlib_uncompress, zlib_crc32 |
| 13 | if zlib._VERSION:match "^lua%-zlib" then | 37 | if zlib._VERSION:match "^lua%-zlib" then |
| 14 | function zlib_compress(data) | 38 | function zlib_compress(data, mode) |
| 15 | return (zlib.deflate()(data, "finish")) | 39 | return (zlib.deflate(6, mode_to_windowbits(mode))(data, "finish")) |
| 40 | end | ||
| 41 | |||
| 42 | function zlib_uncompress(data, mode) | ||
| 43 | return (zlib.inflate(mode_to_windowbits(mode))(data)) | ||
| 16 | end | 44 | end |
| 17 | 45 | ||
| 18 | function zlib_crc32(data) | 46 | function zlib_crc32(data) |
| 19 | return zlib.crc32()(data) | 47 | return zlib.crc32()(data) |
| 20 | end | 48 | end |
| 21 | elseif zlib._VERSION:match "^lzlib" then | 49 | elseif zlib._VERSION:match "^lzlib" then |
| 22 | function zlib_compress(data) | 50 | function zlib_compress(data, mode) |
| 23 | return zlib.compress(data) | 51 | return zlib.compress(data, -1, nil, mode_to_windowbits(mode)) |
| 52 | end | ||
| 53 | |||
| 54 | function zlib_uncompress(data, mode) | ||
| 55 | return zlib.decompress(data, mode_to_windowbits(mode)) | ||
| 24 | end | 56 | end |
| 25 | 57 | ||
| 26 | function zlib_crc32(data) | 58 | function zlib_crc32(data) |
| @@ -30,7 +62,7 @@ else | |||
| 30 | error("unknown zlib library", 0) | 62 | error("unknown zlib library", 0) |
| 31 | end | 63 | end |
| 32 | 64 | ||
| 33 | local function number_to_bytestring(number, nbytes) | 65 | local function number_to_lestring(number, nbytes) |
| 34 | local out = {} | 66 | local out = {} |
| 35 | for _ = 1, nbytes do | 67 | for _ = 1, nbytes do |
| 36 | local byte = number % 256 | 68 | local byte = number % 256 |
| @@ -40,6 +72,20 @@ local function number_to_bytestring(number, nbytes) | |||
| 40 | return table.concat(out) | 72 | return table.concat(out) |
| 41 | end | 73 | end |
| 42 | 74 | ||
| 75 | local function lestring_to_number(str) | ||
| 76 | local n = 0 | ||
| 77 | local bytes = { string.byte(str, 1, #str) } | ||
| 78 | for b = 1, #str do | ||
| 79 | n = n + shl(bytes[b], (b-1)*8) | ||
| 80 | end | ||
| 81 | return math.floor(n) | ||
| 82 | end | ||
| 83 | |||
| 84 | local LOCAL_FILE_HEADER_SIGNATURE = number_to_lestring(0x04034b50, 4) | ||
| 85 | local DATA_DESCRIPTOR_SIGNATURE = number_to_lestring(0x08074b50, 4) | ||
| 86 | local CENTRAL_DIRECTORY_SIGNATURE = number_to_lestring(0x02014b50, 4) | ||
| 87 | local END_OF_CENTRAL_DIR_SIGNATURE = number_to_lestring(0x06054b50, 4) | ||
| 88 | |||
| 43 | --- Begin a new file to be stored inside the zipfile. | 89 | --- Begin a new file to be stored inside the zipfile. |
| 44 | -- @param self handle of the zipfile being written. | 90 | -- @param self handle of the zipfile being written. |
| 45 | -- @param filename filenome of the file to be added to the zipfile. | 91 | -- @param filename filenome of the file to be added to the zipfile. |
| @@ -56,7 +102,7 @@ local function zipwriter_open_new_file_in_zip(self, filename) | |||
| 56 | lfh.file_name_length = #filename | 102 | lfh.file_name_length = #filename |
| 57 | lfh.extra_field_length = 0 | 103 | lfh.extra_field_length = 0 |
| 58 | lfh.file_name = filename:gsub("\\", "/") | 104 | lfh.file_name = filename:gsub("\\", "/") |
| 59 | lfh.external_attr = 0 -- TODO properly store permissions | 105 | lfh.external_attr = shl(493, 16) -- TODO proper permissions |
| 60 | self.in_open_file = true | 106 | self.in_open_file = true |
| 61 | return true | 107 | return true |
| 62 | end | 108 | end |
| @@ -70,7 +116,7 @@ local function zipwriter_write_file_in_zip(self, data) | |||
| 70 | return nil | 116 | return nil |
| 71 | end | 117 | end |
| 72 | local lfh = self.local_file_header | 118 | local lfh = self.local_file_header |
| 73 | local compressed = zlib_compress(data):sub(3, -5) | 119 | local compressed = zlib_compress(data, "raw") |
| 74 | lfh.crc32 = zlib_crc32(data) | 120 | lfh.crc32 = zlib_crc32(data) |
| 75 | lfh.compressed_size = #compressed | 121 | lfh.compressed_size = #compressed |
| 76 | lfh.uncompressed_size = #data | 122 | lfh.uncompressed_size = #data |
| @@ -91,26 +137,27 @@ local function zipwriter_close_file_in_zip(self) | |||
| 91 | -- Local file header | 137 | -- Local file header |
| 92 | local lfh = self.local_file_header | 138 | local lfh = self.local_file_header |
| 93 | lfh.offset = zh:seek() | 139 | lfh.offset = zh:seek() |
| 94 | zh:write(number_to_bytestring(0x04034b50, 4)) -- signature | 140 | zh:write(LOCAL_FILE_HEADER_SIGNATURE) |
| 95 | zh:write(number_to_bytestring(20, 2)) -- version needed to extract: 2.0 | 141 | zh:write(number_to_lestring(20, 2)) -- version needed to extract: 2.0 |
| 96 | zh:write(number_to_bytestring(0, 2)) -- general purpose bit flag | 142 | zh:write(number_to_lestring(4, 2)) -- general purpose bit flag |
| 97 | zh:write(number_to_bytestring(8, 2)) -- compression method: deflate | 143 | zh:write(number_to_lestring(8, 2)) -- compression method: deflate |
| 98 | zh:write(number_to_bytestring(lfh.last_mod_file_time, 2)) | 144 | zh:write(number_to_lestring(lfh.last_mod_file_time, 2)) |
| 99 | zh:write(number_to_bytestring(lfh.last_mod_file_date, 2)) | 145 | zh:write(number_to_lestring(lfh.last_mod_file_date, 2)) |
| 100 | zh:write(number_to_bytestring(lfh.crc32, 4)) | 146 | zh:write(number_to_lestring(lfh.crc32, 4)) |
| 101 | zh:write(number_to_bytestring(lfh.compressed_size, 4)) | 147 | zh:write(number_to_lestring(lfh.compressed_size, 4)) |
| 102 | zh:write(number_to_bytestring(lfh.uncompressed_size, 4)) | 148 | zh:write(number_to_lestring(lfh.uncompressed_size, 4)) |
| 103 | zh:write(number_to_bytestring(lfh.file_name_length, 2)) | 149 | zh:write(number_to_lestring(lfh.file_name_length, 2)) |
| 104 | zh:write(number_to_bytestring(lfh.extra_field_length, 2)) | 150 | zh:write(number_to_lestring(lfh.extra_field_length, 2)) |
| 105 | zh:write(lfh.file_name) | 151 | zh:write(lfh.file_name) |
| 106 | 152 | ||
| 107 | -- File data | 153 | -- File data |
| 108 | zh:write(self.data) | 154 | zh:write(self.data) |
| 109 | 155 | ||
| 110 | -- Data descriptor | 156 | -- Data descriptor |
| 111 | zh:write(number_to_bytestring(lfh.crc32, 4)) | 157 | zh:write(DATA_DESCRIPTOR_SIGNATURE) |
| 112 | zh:write(number_to_bytestring(lfh.compressed_size, 4)) | 158 | zh:write(number_to_lestring(lfh.crc32, 4)) |
| 113 | zh:write(number_to_bytestring(lfh.uncompressed_size, 4)) | 159 | zh:write(number_to_lestring(lfh.compressed_size, 4)) |
| 160 | zh:write(number_to_lestring(lfh.uncompressed_size, 4)) | ||
| 114 | 161 | ||
| 115 | table.insert(self.files, lfh) | 162 | table.insert(self.files, lfh) |
| 116 | self.in_open_file = false | 163 | self.in_open_file = false |
| @@ -167,36 +214,36 @@ local function zipwriter_close(self) | |||
| 167 | local size_of_central_directory = 0 | 214 | local size_of_central_directory = 0 |
| 168 | -- Central directory structure | 215 | -- Central directory structure |
| 169 | for _, lfh in ipairs(self.files) do | 216 | for _, lfh in ipairs(self.files) do |
| 170 | zh:write(number_to_bytestring(0x02014b50, 4)) -- signature | 217 | zh:write(CENTRAL_DIRECTORY_SIGNATURE) -- signature |
| 171 | zh:write(number_to_bytestring(3, 2)) -- version made by: UNIX | 218 | zh:write(number_to_lestring(3, 2)) -- version made by: UNIX |
| 172 | zh:write(number_to_bytestring(20, 2)) -- version needed to extract: 2.0 | 219 | zh:write(number_to_lestring(20, 2)) -- version needed to extract: 2.0 |
| 173 | zh:write(number_to_bytestring(0, 2)) -- general purpose bit flag | 220 | zh:write(number_to_lestring(0, 2)) -- general purpose bit flag |
| 174 | zh:write(number_to_bytestring(8, 2)) -- compression method: deflate | 221 | zh:write(number_to_lestring(8, 2)) -- compression method: deflate |
| 175 | zh:write(number_to_bytestring(lfh.last_mod_file_time, 2)) | 222 | zh:write(number_to_lestring(lfh.last_mod_file_time, 2)) |
| 176 | zh:write(number_to_bytestring(lfh.last_mod_file_date, 2)) | 223 | zh:write(number_to_lestring(lfh.last_mod_file_date, 2)) |
| 177 | zh:write(number_to_bytestring(lfh.crc32, 4)) | 224 | zh:write(number_to_lestring(lfh.crc32, 4)) |
| 178 | zh:write(number_to_bytestring(lfh.compressed_size, 4)) | 225 | zh:write(number_to_lestring(lfh.compressed_size, 4)) |
| 179 | zh:write(number_to_bytestring(lfh.uncompressed_size, 4)) | 226 | zh:write(number_to_lestring(lfh.uncompressed_size, 4)) |
| 180 | zh:write(number_to_bytestring(lfh.file_name_length, 2)) | 227 | zh:write(number_to_lestring(lfh.file_name_length, 2)) |
| 181 | zh:write(number_to_bytestring(lfh.extra_field_length, 2)) | 228 | zh:write(number_to_lestring(lfh.extra_field_length, 2)) |
| 182 | zh:write(number_to_bytestring(0, 2)) -- file comment length | 229 | zh:write(number_to_lestring(0, 2)) -- file comment length |
| 183 | zh:write(number_to_bytestring(0, 2)) -- disk number start | 230 | zh:write(number_to_lestring(0, 2)) -- disk number start |
| 184 | zh:write(number_to_bytestring(0, 2)) -- internal file attributes | 231 | zh:write(number_to_lestring(0, 2)) -- internal file attributes |
| 185 | zh:write(number_to_bytestring(lfh.external_attr, 4)) -- external file attributes | 232 | zh:write(number_to_lestring(lfh.external_attr, 4)) -- external file attributes |
| 186 | zh:write(number_to_bytestring(lfh.offset, 4)) -- relative offset of local header | 233 | zh:write(number_to_lestring(lfh.offset, 4)) -- relative offset of local header |
| 187 | zh:write(lfh.file_name) | 234 | zh:write(lfh.file_name) |
| 188 | size_of_central_directory = size_of_central_directory + 46 + lfh.file_name_length | 235 | size_of_central_directory = size_of_central_directory + 46 + lfh.file_name_length |
| 189 | end | 236 | end |
| 190 | 237 | ||
| 191 | -- End of central directory record | 238 | -- End of central directory record |
| 192 | zh:write(number_to_bytestring(0x06054b50, 4)) -- signature | 239 | zh:write(END_OF_CENTRAL_DIR_SIGNATURE) -- signature |
| 193 | zh:write(number_to_bytestring(0, 2)) -- number of this disk | 240 | zh:write(number_to_lestring(0, 2)) -- number of this disk |
| 194 | zh:write(number_to_bytestring(0, 2)) -- number of disk with start of central directory | 241 | zh:write(number_to_lestring(0, 2)) -- number of disk with start of central directory |
| 195 | zh:write(number_to_bytestring(#self.files, 2)) -- total number of entries in the central dir on this disk | 242 | zh:write(number_to_lestring(#self.files, 2)) -- total number of entries in the central dir on this disk |
| 196 | zh:write(number_to_bytestring(#self.files, 2)) -- total number of entries in the central dir | 243 | zh:write(number_to_lestring(#self.files, 2)) -- total number of entries in the central dir |
| 197 | zh:write(number_to_bytestring(size_of_central_directory, 4)) | 244 | zh:write(number_to_lestring(size_of_central_directory, 4)) |
| 198 | zh:write(number_to_bytestring(central_directory_offset, 4)) | 245 | zh:write(number_to_lestring(central_directory_offset, 4)) |
| 199 | zh:write(number_to_bytestring(0, 2)) -- zip file comment length | 246 | zh:write(number_to_lestring(0, 2)) -- zip file comment length |
| 200 | zh:close() | 247 | zh:close() |
| 201 | 248 | ||
| 202 | return true | 249 | return true |
| @@ -253,12 +300,231 @@ function zip.zip(zipfile, ...) | |||
| 253 | end | 300 | end |
| 254 | end | 301 | end |
| 255 | 302 | ||
| 256 | ok = zw:close() | 303 | zw:close() |
| 304 | return ok, err | ||
| 305 | end | ||
| 306 | |||
| 307 | |||
| 308 | local function ziptime_to_luatime(ztime, zdate) | ||
| 309 | return { | ||
| 310 | year = shr(zdate, 9) + 1980, | ||
| 311 | month = shr(lowbits(zdate, 9), 5), | ||
| 312 | day = lowbits(zdate, 5), | ||
| 313 | hour = shr(ztime, 11), | ||
| 314 | min = shr(lowbits(ztime, 11), 5), | ||
| 315 | sec = lowbits(ztime, 5) * 2, | ||
| 316 | } | ||
| 317 | end | ||
| 318 | |||
| 319 | local function read_file_in_zip(zh, cdr) | ||
| 320 | local sig = zh:read(4) | ||
| 321 | if sig ~= LOCAL_FILE_HEADER_SIGNATURE then | ||
| 322 | return nil, "failed reading Local File Header signature" | ||
| 323 | end | ||
| 324 | |||
| 325 | local lfh = {} | ||
| 326 | lfh.version_needed = lestring_to_number(zh:read(2)) | ||
| 327 | lfh.bitflag = lestring_to_number(zh:read(2)) | ||
| 328 | lfh.compression_method = lestring_to_number(zh:read(2)) | ||
| 329 | lfh.last_mod_file_time = lestring_to_number(zh:read(2)) | ||
| 330 | lfh.last_mod_file_date = lestring_to_number(zh:read(2)) | ||
| 331 | lfh.crc32 = lestring_to_number(zh:read(4)) | ||
| 332 | lfh.compressed_size = lestring_to_number(zh:read(4)) | ||
| 333 | lfh.uncompressed_size = lestring_to_number(zh:read(4)) | ||
| 334 | lfh.file_name_length = lestring_to_number(zh:read(2)) | ||
| 335 | lfh.extra_field_length = lestring_to_number(zh:read(2)) | ||
| 336 | lfh.file_name = zh:read(lfh.file_name_length) | ||
| 337 | lfh.extra_field = zh:read(lfh.extra_field_length) | ||
| 338 | |||
| 339 | local data = zh:read(cdr.compressed_size) | ||
| 340 | |||
| 341 | local uncompressed | ||
| 342 | if cdr.compression_method == 8 then | ||
| 343 | uncompressed = zlib_uncompress(data, "raw") | ||
| 344 | elseif cdr.compression_method == 0 then | ||
| 345 | uncompressed = data | ||
| 346 | else | ||
| 347 | return nil, "unknown compression method " .. cdr.compression_method | ||
| 348 | end | ||
| 349 | |||
| 350 | if #uncompressed ~= cdr.uncompressed_size then | ||
| 351 | return nil, "uncompressed size doesn't match" | ||
| 352 | end | ||
| 353 | if cdr.crc32 ~= zlib_crc32(uncompressed) then | ||
| 354 | return nil, "crc32 failed (expected " .. cdr.crc32 .. ") - data: " .. uncompressed | ||
| 355 | end | ||
| 356 | |||
| 357 | return uncompressed | ||
| 358 | end | ||
| 359 | |||
| 360 | local function process_end_of_central_dir(zh) | ||
| 361 | local at, err = zh:seek("end", -22) | ||
| 362 | if not at then | ||
| 363 | return nil, err | ||
| 364 | end | ||
| 365 | |||
| 366 | while true do | ||
| 367 | local sig = zh:read(4) | ||
| 368 | if sig == END_OF_CENTRAL_DIR_SIGNATURE then | ||
| 369 | break | ||
| 370 | end | ||
| 371 | at = at - 1 | ||
| 372 | local at1, err = zh:seek("set", at) | ||
| 373 | if at1 ~= at then | ||
| 374 | return nil, "Could not find End of Central Directory signature" | ||
| 375 | end | ||
| 376 | end | ||
| 377 | |||
| 378 | -- number of this disk (2 bytes) | ||
| 379 | -- number of the disk with the start of the central directory (2 bytes) | ||
| 380 | -- total number of entries in the central directory on this disk (2 bytes) | ||
| 381 | -- total number of entries in the central directory (2 bytes) | ||
| 382 | zh:seek("cur", 6) | ||
| 383 | |||
| 384 | local central_directory_entries = lestring_to_number(zh:read(2)) | ||
| 385 | |||
| 386 | -- central directory size (4 bytes) | ||
| 387 | zh:seek("cur", 4) | ||
| 388 | |||
| 389 | local central_directory_offset = lestring_to_number(zh:read(4)) | ||
| 390 | |||
| 391 | return central_directory_entries, central_directory_offset | ||
| 392 | end | ||
| 393 | |||
| 394 | local function process_central_dir(zh, cd_entries) | ||
| 395 | |||
| 396 | local files = {} | ||
| 397 | |||
| 398 | for i = 1, cd_entries do | ||
| 399 | local sig = zh:read(4) | ||
| 400 | if sig ~= CENTRAL_DIRECTORY_SIGNATURE then | ||
| 401 | return nil, "failed reading Central Directory signature" | ||
| 402 | end | ||
| 403 | |||
| 404 | local cdr = {} | ||
| 405 | files[i] = cdr | ||
| 406 | |||
| 407 | cdr.version_made_by = lestring_to_number(zh:read(2)) | ||
| 408 | cdr.version_needed = lestring_to_number(zh:read(2)) | ||
| 409 | cdr.bitflag = lestring_to_number(zh:read(2)) | ||
| 410 | cdr.compression_method = lestring_to_number(zh:read(2)) | ||
| 411 | cdr.last_mod_file_time = lestring_to_number(zh:read(2)) | ||
| 412 | cdr.last_mod_file_date = lestring_to_number(zh:read(2)) | ||
| 413 | cdr.last_mod_luatime = ziptime_to_luatime(cdr.last_mod_file_time, cdr.last_mod_file_date) | ||
| 414 | cdr.crc32 = lestring_to_number(zh:read(4)) | ||
| 415 | cdr.compressed_size = lestring_to_number(zh:read(4)) | ||
| 416 | cdr.uncompressed_size = lestring_to_number(zh:read(4)) | ||
| 417 | cdr.file_name_length = lestring_to_number(zh:read(2)) | ||
| 418 | cdr.extra_field_length = lestring_to_number(zh:read(2)) | ||
| 419 | cdr.file_comment_length = lestring_to_number(zh:read(2)) | ||
| 420 | cdr.disk_number_start = lestring_to_number(zh:read(2)) | ||
| 421 | cdr.internal_attr = lestring_to_number(zh:read(2)) | ||
| 422 | cdr.external_attr = lestring_to_number(zh:read(4)) | ||
| 423 | cdr.offset = lestring_to_number(zh:read(4)) | ||
| 424 | cdr.file_name = zh:read(cdr.file_name_length) | ||
| 425 | cdr.extra_field = zh:read(cdr.extra_field_length) | ||
| 426 | cdr.file_comment = zh:read(cdr.file_comment_length) | ||
| 427 | end | ||
| 428 | return files | ||
| 429 | end | ||
| 430 | |||
| 431 | --- Uncompress files from a .zip archive. | ||
| 432 | -- @param zipfile string: pathname of .zip archive to be created. | ||
| 433 | -- @return boolean or (boolean, string): true on success, | ||
| 434 | -- false and an error message on failure. | ||
| 435 | function zip.unzip(zipfile) | ||
| 436 | zipfile = fs.absolute_name(zipfile) | ||
| 437 | local zh, err = io.open(zipfile, "rb") | ||
| 438 | if not zh then | ||
| 439 | return nil, err | ||
| 440 | end | ||
| 441 | |||
| 442 | local cd_entries, cd_offset = process_end_of_central_dir(zh) | ||
| 443 | if not cd_entries then | ||
| 444 | return nil, cd_offset | ||
| 445 | end | ||
| 446 | |||
| 447 | local ok, err = zh:seek("set", cd_offset) | ||
| 257 | if not ok then | 448 | if not ok then |
| 258 | return false, "error closing "..zipfile | 449 | return nil, err |
| 259 | end | 450 | end |
| 260 | return ok, err | 451 | |
| 452 | local files, err = process_central_dir(zh, cd_entries) | ||
| 453 | if not files then | ||
| 454 | return nil, err | ||
| 455 | end | ||
| 456 | |||
| 457 | for _, cdr in ipairs(files) do | ||
| 458 | local file = cdr.file_name | ||
| 459 | if file:sub(#file) == "/" then | ||
| 460 | local ok, err = fs.make_dir(dir.path(fs.current_dir(), file)) | ||
| 461 | if not ok then | ||
| 462 | return nil, err | ||
| 463 | end | ||
| 464 | else | ||
| 465 | local base = dir.dir_name(file) | ||
| 466 | if base ~= "" then | ||
| 467 | base = dir.path(fs.current_dir(), base) | ||
| 468 | if not fs.is_dir(base) then | ||
| 469 | local ok, err = fs.make_dir(base) | ||
| 470 | if not ok then | ||
| 471 | return nil, err | ||
| 472 | end | ||
| 473 | end | ||
| 474 | end | ||
| 475 | |||
| 476 | local ok, err = zh:seek("set", cdr.offset) | ||
| 477 | if not ok then | ||
| 478 | return nil, err | ||
| 479 | end | ||
| 480 | |||
| 481 | local contents, err = read_file_in_zip(zh, cdr) | ||
| 482 | if not contents then | ||
| 483 | return nil, err | ||
| 484 | end | ||
| 485 | local pathname = dir.path(fs.current_dir(), file) | ||
| 486 | local wf, err = io.open(pathname, "wb") | ||
| 487 | if not wf then | ||
| 488 | zh:close() | ||
| 489 | return nil, err | ||
| 490 | end | ||
| 491 | wf:write(contents) | ||
| 492 | wf:close() | ||
| 493 | |||
| 494 | if cdr.external_attr > 0 then | ||
| 495 | fs.set_permissions(pathname, "exec", "all") | ||
| 496 | else | ||
| 497 | fs.set_permissions(pathname, "read", "all") | ||
| 498 | end | ||
| 499 | fs.set_time(pathname, cdr.last_mod_luatime) | ||
| 500 | end | ||
| 501 | end | ||
| 502 | zh:close() | ||
| 503 | return true | ||
| 261 | end | 504 | end |
| 262 | 505 | ||
| 506 | function zip.gzip(input_filename, output_filename) | ||
| 507 | assert(type(input_filename) == "string") | ||
| 508 | assert(output_filename == nil or type(output_filename) == "string") | ||
| 509 | |||
| 510 | if not output_filename then | ||
| 511 | output_filename = input_filename .. ".gz" | ||
| 512 | end | ||
| 513 | |||
| 514 | local fn = fun.partial(fun.flip(zlib_compress), "gzip") | ||
| 515 | return fs.filter_file(fn, input_filename, output_filename) | ||
| 516 | end | ||
| 517 | |||
| 518 | function zip.gunzip(input_filename, output_filename) | ||
| 519 | assert(type(input_filename) == "string") | ||
| 520 | assert(output_filename == nil or type(output_filename) == "string") | ||
| 521 | |||
| 522 | if not output_filename then | ||
| 523 | output_filename = input_filename:gsub("%.gz$", "") | ||
| 524 | end | ||
| 525 | |||
| 526 | local fn = fun.partial(fun.flip(zlib_uncompress), "gzip") | ||
| 527 | return fs.filter_file(fn, input_filename, output_filename) | ||
| 528 | end | ||
| 263 | 529 | ||
| 264 | return zip | 530 | return zip |
