10
11
local util = require("luarocks.util")
11
12
local path = require("luarocks.path")
13
local socket_ok, http = pcall(require, "socket.http")
14
local _, ftp = pcall(require, "socket.ftp")
15
local zip_ok, lrzip = pcall(require, "luarocks.tools.zip")
16
local unzip_ok, luazip = pcall(require, "zip"); _G.zip = nil
17
local lfs_ok, lfs = pcall(require, "lfs")
18
local md5_ok, md5 = pcall(require, "md5")
19
local posix_ok, posix = pcall(require, "posix")
21
local tar = require("luarocks.tools.tar")
14
local socket_ok, zip_ok, unzip_ok, lfs_ok, md5_ok, posix_ok, _
15
local http, ftp, lrzip, luazip, lfs, md5, posix
17
if cfg.fs_use_modules then
18
socket_ok, http = pcall(require, "socket.http")
19
_, ftp = pcall(require, "socket.ftp")
20
zip_ok, lrzip = pcall(require, "luarocks.tools.zip")
21
unzip_ok, luazip = pcall(require, "zip"); _G.zip = nil
22
lfs_ok, lfs = pcall(require, "lfs")
23
md5_ok, md5 = pcall(require, "md5")
24
posix_ok, posix = pcall(require, "posix")
22
27
local patch = require("luarocks.tools.patch")
24
29
local dir_stack = {}
26
31
math.randomseed(os.time())
33
local dir_separator = "/"
30
35
--- Quote argument for shell processing.
31
36
-- Adds single quotes and escapes.
32
37
-- @param arg string: Unquoted argument.
33
38
-- @return string: Quoted argument.
39
function fs_lua.Q(arg)
35
40
assert(type(arg) == "string")
37
42
-- FIXME Unix-specific
38
return "'" .. arg:gsub("\\", "\\\\"):gsub("'", "'\\''") .. "'"
41
local function normalize(name)
42
return name:gsub("\\", "/"):gsub("/$", "")
43
return "'" .. arg:gsub("'", "'\\''") .. "'"
45
46
--- Test is file/dir is writable.
69
70
--- Create a temporary directory.
70
71
-- @param name string: name pattern to use for avoiding conflicts
71
72
-- when creating temporary directory.
72
function make_temp_dir(name)
73
-- @return string or (nil, string): name of temporary directory or (nil, error message) on failure.
74
function fs_lua.make_temp_dir(name)
73
75
assert(type(name) == "string")
74
name = normalize(name)
76
name = dir.normalize(name)
76
78
local temp_dir = (os.getenv("TMP") or "/tmp") .. "/luarocks_" .. name:gsub(dir.separator, "_") .. "-" .. tostring(math.floor(math.random() * 10000))
77
if fs.make_dir(temp_dir) then
79
local ok, err = fs.make_dir(temp_dir)
87
local function quote_args(command, ...)
88
local out = { command }
89
for _, arg in ipairs({...}) do
90
assert(type(arg) == "string")
91
out[#out+1] = fs.Q(arg)
93
return table.concat(out, " ")
84
96
--- Run the given command, quoting its arguments.
89
100
-- @param ... Strings containing additional arguments, which are quoted.
90
101
-- @return boolean: true if command succeeds (status code 0), false
92
function execute(command, ...)
103
function fs_lua.execute(command, ...)
93
104
assert(type(command) == "string")
105
return fs.execute_string(quote_args(command, ...))
95
for _, arg in ipairs({...}) do
96
assert(type(arg) == "string")
97
command = command .. " " .. fs.Q(arg)
108
--- Run the given command, quoting its arguments, silencing its output.
109
-- The command is executed in the current directory in the dir stack.
110
-- Silencing is omitted if 'verbose' mode is enabled.
111
-- @param command string: The command to be executed. No quoting/escaping
113
-- @param ... Strings containing additional arguments, which will be quoted.
114
-- @return boolean: true if command succeeds (status code 0), false
116
function fs_lua.execute_quiet(command, ...)
117
assert(type(command) == "string")
118
if cfg.verbose then -- omit silencing output
119
return fs.execute_string(quote_args(command, ...))
121
return fs.execute_string(fs.quiet(quote_args(command, ...)))
99
return fs.execute_string(command)
102
125
--- Check the MD5 checksum for a file.
103
126
-- @param file string: The file to be checked.
104
127
-- @param md5sum string: The string with the expected MD5 checksum.
128
-- @return boolean: true if the MD5 checksum for 'file' equals 'md5sum', false + msg if not
105
129
-- or if it could not perform the check for any reason.
106
function check_md5(file, md5sum)
107
file = normalize(file)
108
local computed = fs.get_md5(file)
130
function fs_lua.check_md5(file, md5sum)
131
file = dir.normalize(file)
132
local computed, msg = fs.get_md5(file)
109
133
if not computed then
112
136
if computed:match("^"..md5sum) then
139
return false, "Mismatch MD5 hash for file "..file
143
--- List the contents of a directory.
144
-- @param at string or nil: directory to list (will be the current
145
-- directory if none is given).
146
-- @return table: an array of strings with the filenames representing
147
-- the contents of a directory.
148
function fs_lua.list_dir(at)
150
for file in fs.dir(at) do
151
result[#result+1] = file
156
--- Iterate over the contents of a directory.
157
-- @param at string or nil: directory to list (will be the current
158
-- directory if none is given).
159
-- @return function: an iterator function suitable for use with
160
-- the for statement.
161
function fs_lua.dir(at)
163
at = fs.current_dir()
165
at = dir.normalize(at)
166
if not fs.is_dir(at) then
167
return function() end
169
return coroutine.wrap(function() fs.dir_iterator(at) end)
119
172
---------------------------------------------------------------------
149
197
-- semantics of chdir, as it does not handle errors the same way,
150
198
-- but works well for our purposes for now.
151
199
-- @param d string: The directory to switch to.
152
function change_dir(d)
200
function fs_lua.change_dir(d)
153
201
table.insert(dir_stack, lfs.currentdir())
158
206
--- Change directory to root.
159
207
-- Allows leaving a directory (e.g. for deleting it) in
160
208
-- a crossplatform way.
161
function change_dir_to_root()
209
function fs_lua.change_dir_to_root()
162
210
table.insert(dir_stack, lfs.currentdir())
163
211
lfs.chdir("/") -- works on Windows too
166
214
--- Change working directory to the previous in the dir stack.
167
215
-- @return true if a pop ocurred, false if the stack was empty.
216
function fs_lua.pop_dir()
169
217
local d = table.remove(dir_stack)
178
226
--- Create a directory if it does not already exist.
227
-- If any of the higher levels in the path name do not exist
179
228
-- too, they are created as well.
180
229
-- @param directory string: pathname of directory to create.
181
function make_dir(directory)
230
-- @return boolean or (boolean, string): true on success or (false, error message) on failure.
231
function fs_lua.make_dir(directory)
182
232
assert(type(directory) == "string")
183
directory = normalize(directory)
233
directory = dir.normalize(directory)
185
235
if directory:sub(2, 2) == ":" then
186
236
path = directory:sub(1, 2)
236
285
-- or nil to use the source filename permissions
237
286
-- @return boolean or (boolean, string): true on success, false on failure,
238
287
-- plus an error message.
239
function copy(src, dest, perms)
288
function fs_lua.copy(src, dest, perms)
240
289
assert(src and dest)
242
dest = normalize(dest)
290
src = dir.normalize(src)
291
dest = dir.normalize(dest)
243
292
local destmode = lfs.attributes(dest, "mode")
244
293
if destmode == "directory" then
245
294
dest = dir.path(dest, dir.base_name(src))
312
362
-- @return boolean or (boolean, string): true on success,
313
363
-- or nil and an error message on failure.
314
364
local function recursive_delete(name)
315
local mode = lfs.attributes(name, "mode")
317
if mode == "file" then
318
return os.remove(name)
319
elseif mode == "directory" then
365
local ok = os.remove(name)
366
if ok then return true end
367
local pok, ok, err = pcall(function()
320
368
for file in lfs.dir(name) do
321
369
if file ~= "." and file ~= ".." then
322
370
local ok, err = recursive_delete(dir.path(name, file))
326
374
local ok, err = lfs.rmdir(name)
327
if not ok then return nil, err end
375
return ok, (not ok) and err
332
384
--- Delete a file or a directory and all its contents.
333
385
-- @param name string: Pathname of source
334
function delete(name)
335
name = normalize(name)
336
return recursive_delete(name) or false
387
function fs_lua.delete(name)
388
name = dir.normalize(name)
389
recursive_delete(name)
339
--- List the contents of a directory.
340
function list_dir(at)
341
assert(type(at) == "string" or not at)
343
at = fs.current_dir()
346
if not fs.is_dir(at) then
392
--- Internal implementation function for fs.dir.
393
-- Yields a filename on each iteration.
394
-- @param at string: directory to list
396
function fs_lua.dir_iterator(at)
350
397
for file in lfs.dir(at) do
351
398
if file ~= "." and file ~= ".." then
352
table.insert(result, file)
399
coroutine.yield(file)
358
404
--- Implementation function for recursive find.
400
441
--- Test for existance of a file.
401
442
-- @param file string: filename to test
402
443
-- @return boolean: true if file exists, false otherwise.
403
function exists(file)
444
function fs_lua.exists(file)
405
file = normalize(file)
446
file = dir.normalize(file)
406
447
return type(lfs.attributes(file)) == "table"
409
450
--- Test is pathname is a directory.
410
451
-- @param file string: pathname to test
411
452
-- @return boolean: true if it is a directory, false otherwise.
412
function is_dir(file)
453
function fs_lua.is_dir(file)
414
file = normalize(file)
455
file = dir.normalize(file)
415
456
return lfs.attributes(file, "mode") == "directory"
418
459
--- Test is pathname is a regular file.
419
460
-- @param file string: pathname to test
420
461
-- @return boolean: true if it is a file, false otherwise.
421
function is_file(file)
462
function fs_lua.is_file(file)
423
file = normalize(file)
464
file = dir.normalize(file)
424
465
return lfs.attributes(file, "mode") == "file"
427
function set_time(file, time)
428
file = normalize(file)
468
function fs_lua.set_time(file, time)
469
file = dir.normalize(file)
429
470
return lfs.touch(file, time)
447
488
--- Uncompress files from a .zip archive.
448
489
-- @param zipfile string: pathname of .zip archive to be extracted.
449
490
-- @return boolean: true on success, false on failure.
450
function unzip(zipfile)
491
function fs_lua.unzip(zipfile)
451
492
local zipfile, err = luazip.open(zipfile)
452
493
if not zipfile then return nil, err end
453
494
local files = zipfile:files()
454
495
local file = files()
456
497
if file.filename:sub(#file.filename) == "/" then
457
fs.make_dir(dir.path(fs.current_dir(), file.filename))
498
local ok, err = fs.make_dir(dir.path(fs.current_dir(), file.filename))
499
if not ok then return nil, err end
501
local base = dir.dir_name(file.filename)
503
base = dir.path(fs.current_dir(), base)
504
if not fs.is_dir(base) then
505
local ok, err = fs.make_dir(base)
506
if not ok then return nil, err end
459
509
local rf, err = zipfile:open(file.filename)
460
510
if not rf then zipfile:close(); return nil, err end
461
511
local contents = rf:read("*a")
482
532
local ltn12 = require("ltn12")
483
533
local luasec_ok, https = pcall(require, "ssl.https")
484
535
local redirect_protocols = {
486
537
https = luasec_ok and https,
489
local function http_request(url, http, loop_control)
540
local function request(url, method, http, loop_control)
490
541
local result = {}
491
local res, status, headers, err = http.request { url = url, proxy = cfg.proxy, redirect = false, sink = ltn12.sink.table(result) }
543
local proxy = cfg.proxy
544
if type(proxy) ~= "string" then proxy = nil end
545
-- LuaSocket's http.request crashes when given URLs missing the scheme part.
546
if proxy and not proxy:find("://") then
547
proxy = "http://" .. proxy
550
if cfg.show_downloads then
551
io.write(method.." "..url.." ...\n")
554
if cfg.connection_timeout and cfg.connection_timeout > 0 then
555
http.TIMEOUT = cfg.connection_timeout
557
local res, status, headers, err = http.request {
562
sink = ltn12.sink.table(result),
563
step = cfg.show_downloads and function(...)
571
return ltn12.pump.step(...)
574
["user-agent"] = cfg.user_agent.." via LuaSocket"
577
if cfg.show_downloads then
493
581
return nil, status
494
582
elseif status == 301 or status == 302 then
502
590
return nil, "Redirection loop -- broken URL?"
504
592
loop_control[url] = true
505
return http_request(location, redirect_protocols[protocol], loop_control)
593
return request(location, method, redirect_protocols[protocol], loop_control)
507
return nil, "URL redirected to unsupported protocol - install luasec to get HTTPS support."
595
return nil, "URL redirected to unsupported protocol - install luasec to get HTTPS support.", "https"
511
599
elseif status ~= 200 then
602
return result, status, headers, err
606
local function http_request(url, http, cached)
608
local tsfd = io.open(cached..".timestamp", "r")
610
local timestamp = tsfd:read("*a")
612
local result, status, headers, err = request(url, "HEAD", http)
613
if status == 200 and headers["last-modified"] == timestamp then
617
return nil, status, headers
621
local result, status, headers, err = request(url, "GET", http)
623
if cached and headers["last-modified"] then
624
local tsfd = io.open(cached..".timestamp", "w")
626
tsfd:write(headers["last-modified"])
514
630
return table.concat(result)
632
return nil, status, headers
636
local downloader_warning = false
518
638
--- Download a remote file.
519
639
-- @param url string: URL to be fetched.
520
640
-- @param filename string or nil: this function attempts to detect the
521
641
-- resulting local filename of the remote file as the basename of the URL;
522
642
-- if that is not correct (due to a redirection, for example), the local
523
643
-- filename can be given explicitly as this second argument.
524
function download(url, filename)
644
-- @return (boolean, string): true and the filename on success,
645
-- false and the error message on failure.
646
function fs_lua.download(url, filename, cache)
525
647
assert(type(url) == "string")
526
648
assert(type(filename) == "string" or not filename)
528
filename = dir.path(fs.current_dir(), filename or dir.base_name(url))
650
filename = fs.absolute_name(filename or dir.base_name(url))
652
local content, err, https_err
531
653
if util.starts_with(url, "http:") then
532
content, err = http_request(url, http)
654
content, err, https_err = http_request(url, http, cache and filename)
533
655
elseif util.starts_with(url, "ftp:") then
534
656
content, err = ftp.get(url)
535
657
elseif util.starts_with(url, "https:") then
536
658
if luasec_ok then
537
content, err = http_request(url, https)
659
content, err = http_request(url, https, cache and filename)
539
err = "Unsupported protocol - install luasec to get HTTPS support."
542
664
err = "Unsupported protocol"
667
if not downloader_warning then
668
util.printerr("Warning: falling back to "..cfg.downloader.." - install luasec to get native HTTPS support")
669
downloader_warning = true
671
return fs.use_downloader(url, filename, cache)
673
if cache and content == true then
674
return true, filename
544
676
if not content then
545
677
return false, tostring(err)
562
699
--- Get the MD5 checksum for a file.
563
700
-- @param file string: The file to be computed.
564
function get_md5(file)
701
-- @return string: The MD5 checksum or nil + error
702
function fs_lua.get_md5(file)
565
703
file = fs.absolute_name(file)
566
local file = io.open(file, "rb")
567
if not file then return false end
568
local computed = md5.sumhexa(file:read("*a"))
704
local file_handler = io.open(file, "rb")
705
if not file_handler then return nil, "Failed to open file for reading: "..file end
706
local computed = md5.sumhexa(file_handler:read("*a"))
708
if computed then return computed end
709
return nil, "Failed to compute MD5 hash for file "..file
652
790
-- @param flags table: the flags table passed to run() drivers.
653
791
-- @return boolean or (boolean, string): true on success, false on failure,
654
792
-- plus an error message.
655
function check_command_permissions(flags)
793
function fs_lua.check_command_permissions(flags)
656
794
local root_dir = path.root_dir(cfg.rocks_dir)
659
for _, dir in ipairs { cfg.rocks_dir, root_dir, dir.dir_name(root_dir) } do
797
for _, dir in ipairs { cfg.rocks_dir, root_dir } do
660
798
if fs.exists(dir) and not fs.is_writable(dir) then
662
800
err = "Your user does not have write permissions in " .. dir
804
local root_parent = dir.dir_name(root_dir)
805
if ok and not fs.exists(root_dir) and not fs.is_writable(root_parent) then
807
err = root_dir.." does not exist and your user does not have write permissions in " .. root_parent
821
--- Check whether a file is a Lua script
822
-- When the file can be succesfully compiled by the configured
823
-- Lua interpreter, it's considered to be a valid Lua file.
824
-- @param name filename of file to check
825
-- @return boolean true, if it is a Lua script, false otherwise
826
function fs_lua.is_lua(name)
827
name = name:gsub([[%\]],"/") -- normalize on fw slash to prevent escaping issues
828
local lua = fs.Q(dir.path(cfg.variables["LUA_BINDIR"], cfg.lua_interpreter)) -- get lua interpreter configured
829
-- execute on configured interpreter, might not be the same as the interpreter LR is run on
830
local result = fs.execute_string(lua..[[ -e "if loadfile(']]..name..[[') then os.exit() else os.exit(1) end"]])
831
return (result == true)