comparison share/lua/5.2/luarocks/fs/lua.lua @ 1132:d137f631bad5

<GreyKnight> (cd luabuild/luarocks-2.0.12; make install)
author HackBot
date Fri, 14 Dec 2012 22:24:27 +0000
parents
children
comparison
equal deleted inserted replaced
1131:ff65dfb63263 1132:d137f631bad5
1
2 --- Native Lua implementation of filesystem and platform abstractions,
3 -- using LuaFileSystem, LZLib, MD5 and LuaCurl.
4 module("luarocks.fs.lua", package.seeall)
5
6 local fs = require("luarocks.fs")
7
8 local cfg = require("luarocks.cfg")
9 local dir = require("luarocks.dir")
10 local util = require("luarocks.util")
11 local path = require("luarocks.path")
12
13 local socket_ok, zip_ok, unzip_ok, lfs_ok, md5_ok, posix_ok, _
14 local http, ftp, lrzip, luazip, lfs, md5, posix
15
16 if cfg.fs_use_modules then
17 socket_ok, http = pcall(require, "socket.http")
18 _, ftp = pcall(require, "socket.ftp")
19 zip_ok, lrzip = pcall(require, "luarocks.tools.zip")
20 unzip_ok, luazip = pcall(require, "zip"); _G.zip = nil
21 lfs_ok, lfs = pcall(require, "lfs")
22 md5_ok, md5 = pcall(require, "md5")
23 posix_ok, posix = pcall(require, "posix")
24 end
25
26 local patch = require("luarocks.tools.patch")
27
28 local dir_stack = {}
29
30 math.randomseed(os.time())
31
32 dir_separator = "/"
33
34 --- Quote argument for shell processing.
35 -- Adds single quotes and escapes.
36 -- @param arg string: Unquoted argument.
37 -- @return string: Quoted argument.
38 function Q(arg)
39 assert(type(arg) == "string")
40
41 -- FIXME Unix-specific
42 return "'" .. arg:gsub("\\", "\\\\"):gsub("'", "'\\''") .. "'"
43 end
44
45 --- Test is file/dir is writable.
46 -- Warning: testing if a file/dir is writable does not guarantee
47 -- that it will remain writable and therefore it is no replacement
48 -- for checking the result of subsequent operations.
49 -- @param file string: filename to test
50 -- @return boolean: true if file exists, false otherwise.
51 function is_writable(file)
52 assert(file)
53 file = dir.normalize(file)
54 local result
55 if fs.is_dir(file) then
56 local file2 = dir.path(file, '.tmpluarockstestwritable')
57 local fh = io.open(file2, 'wb')
58 result = fh ~= nil
59 if fh then fh:close() end
60 os.remove(file2)
61 else
62 local fh = io.open(file, 'r+b')
63 result = fh ~= nil
64 if fh then fh:close() end
65 end
66 return result
67 end
68
69 --- Create a temporary directory.
70 -- @param name string: name pattern to use for avoiding conflicts
71 -- when creating temporary directory.
72 -- @return string or nil: name of temporary directory or nil on failure.
73 function make_temp_dir(name)
74 assert(type(name) == "string")
75 name = dir.normalize(name)
76
77 local temp_dir = (os.getenv("TMP") or "/tmp") .. "/luarocks_" .. name:gsub(dir.separator, "_") .. "-" .. tostring(math.floor(math.random() * 10000))
78 if fs.make_dir(temp_dir) then
79 return temp_dir
80 else
81 return nil
82 end
83 end
84
85 --- Run the given command, quoting its arguments.
86 -- The command is executed in the current directory in the dir stack.
87 -- @param command string: The command to be executed. No quoting/escaping
88 -- is applied.
89 -- @param ... Strings containing additional arguments, which are quoted.
90 -- @return boolean: true if command succeeds (status code 0), false
91 -- otherwise.
92 function execute(command, ...)
93 assert(type(command) == "string")
94
95 for _, arg in ipairs({...}) do
96 assert(type(arg) == "string")
97 command = command .. " " .. fs.Q(arg)
98 end
99 return fs.execute_string(command)
100 end
101
102 --- Check the MD5 checksum for a file.
103 -- @param file string: The file to be checked.
104 -- @param md5sum string: The string with the expected MD5 checksum.
105 -- @return boolean: true if the MD5 checksum for 'file' equals 'md5sum', false if not
106 -- or if it could not perform the check for any reason.
107 function check_md5(file, md5sum)
108 file = dir.normalize(file)
109 local computed = fs.get_md5(file)
110 if not computed then
111 return false
112 end
113 if computed:match("^"..md5sum) then
114 return true
115 else
116 return false
117 end
118 end
119
120 ---------------------------------------------------------------------
121 -- LuaFileSystem functions
122 ---------------------------------------------------------------------
123
124 if lfs_ok then
125
126 --- Run the given command.
127 -- The command is executed in the current directory in the dir stack.
128 -- @param cmd string: No quoting/escaping is applied to the command.
129 -- @return boolean: true if command succeeds (status code 0), false
130 -- otherwise.
131 function execute_string(cmd)
132 local code = os.execute(cmd)
133 if code == 0 or code == true then
134 return true
135 else
136 return false
137 end
138 end
139
140 --- Obtain current directory.
141 -- Uses the module's internal dir stack.
142 -- @return string: the absolute pathname of the current directory.
143 function current_dir()
144 return lfs.currentdir()
145 end
146
147 --- Change the current directory.
148 -- Uses the module's internal dir stack. This does not have exact
149 -- semantics of chdir, as it does not handle errors the same way,
150 -- but works well for our purposes for now.
151 -- @param d string: The directory to switch to.
152 function change_dir(d)
153 table.insert(dir_stack, lfs.currentdir())
154 d = dir.normalize(d)
155 lfs.chdir(d)
156 end
157
158 --- Change directory to root.
159 -- Allows leaving a directory (e.g. for deleting it) in
160 -- a crossplatform way.
161 function change_dir_to_root()
162 table.insert(dir_stack, lfs.currentdir())
163 lfs.chdir("/") -- works on Windows too
164 end
165
166 --- Change working directory to the previous in the dir stack.
167 -- @return true if a pop ocurred, false if the stack was empty.
168 function pop_dir()
169 local d = table.remove(dir_stack)
170 if d then
171 lfs.chdir(d)
172 return true
173 else
174 return false
175 end
176 end
177
178 --- Create a directory if it does not already exist.
179 -- If any of the higher levels in the path name does not exist
180 -- too, they are created as well.
181 -- @param directory string: pathname of directory to create.
182 -- @return boolean: true on success, false on failure.
183 function make_dir(directory)
184 assert(type(directory) == "string")
185 directory = dir.normalize(directory)
186 local path = nil
187 if directory:sub(2, 2) == ":" then
188 path = directory:sub(1, 2)
189 directory = directory:sub(4)
190 else
191 if directory:match("^/") then
192 path = ""
193 end
194 end
195 for d in directory:gmatch("([^"..dir.separator.."]+)"..dir.separator.."*") do
196 path = path and path .. dir.separator .. d or d
197 local mode = lfs.attributes(path, "mode")
198 if not mode then
199 if not lfs.mkdir(path) then
200 return false
201 end
202 elseif mode ~= "directory" then
203 return false
204 end
205 end
206 return true
207 end
208
209 --- Remove a directory if it is empty.
210 -- Does not return errors (for example, if directory is not empty or
211 -- if already does not exist)
212 -- @param d string: pathname of directory to remove.
213 function remove_dir_if_empty(d)
214 assert(d)
215 d = dir.normalize(d)
216 lfs.rmdir(d)
217 end
218
219 --- Remove a directory if it is empty.
220 -- Does not return errors (for example, if directory is not empty or
221 -- if already does not exist)
222 -- @param d string: pathname of directory to remove.
223 function remove_dir_tree_if_empty(d)
224 assert(d)
225 d = dir.normalize(d)
226 for i=1,10 do
227 lfs.rmdir(d)
228 d = dir.dir_name(d)
229 end
230 end
231
232 --- Copy a file.
233 -- @param src string: Pathname of source
234 -- @param dest string: Pathname of destination
235 -- @param perms string or nil: Permissions for destination file,
236 -- or nil to use the source filename permissions
237 -- @return boolean or (boolean, string): true on success, false on failure,
238 -- plus an error message.
239 function copy(src, dest, perms)
240 assert(src and dest)
241 src = dir.normalize(src)
242 dest = dir.normalize(dest)
243 local destmode = lfs.attributes(dest, "mode")
244 if destmode == "directory" then
245 dest = dir.path(dest, dir.base_name(src))
246 end
247 if not perms then perms = fs.get_permissions(src) end
248 local src_h, err = io.open(src, "rb")
249 if not src_h then return nil, err end
250 local dest_h, err = io.open(dest, "w+b")
251 if not dest_h then src_h:close() return nil, err end
252 while true do
253 local block = src_h:read(8192)
254 if not block then break end
255 dest_h:write(block)
256 end
257 src_h:close()
258 dest_h:close()
259 fs.chmod(dest, perms)
260 return true
261 end
262
263 --- Implementation function for recursive copy of directory contents.
264 -- Assumes paths are normalized.
265 -- @param src string: Pathname of source
266 -- @param dest string: Pathname of destination
267 -- @return boolean or (boolean, string): true on success, false on failure
268 local function recursive_copy(src, dest)
269 local srcmode = lfs.attributes(src, "mode")
270
271 if srcmode == "file" then
272 local ok = fs.copy(src, dest)
273 if not ok then return false end
274 elseif srcmode == "directory" then
275 local subdir = dir.path(dest, dir.base_name(src))
276 fs.make_dir(subdir)
277 for file in lfs.dir(src) do
278 if file ~= "." and file ~= ".." then
279 local ok = recursive_copy(dir.path(src, file), subdir)
280 if not ok then return false end
281 end
282 end
283 end
284 return true
285 end
286
287 --- Recursively copy the contents of a directory.
288 -- @param src string: Pathname of source
289 -- @param dest string: Pathname of destination
290 -- @return boolean or (boolean, string): true on success, false on failure,
291 -- plus an error message.
292 function copy_contents(src, dest)
293 assert(src and dest)
294 src = dir.normalize(src)
295 dest = dir.normalize(dest)
296 assert(lfs.attributes(src, "mode") == "directory")
297
298 for file in lfs.dir(src) do
299 if file ~= "." and file ~= ".." then
300 local ok = recursive_copy(dir.path(src, file), dest)
301 if not ok then
302 return false, "Failed copying "..src.." to "..dest
303 end
304 end
305 end
306 return true
307 end
308
309 --- Implementation function for recursive removal of directories.
310 -- Assumes paths are normalized.
311 -- @param name string: Pathname of file
312 -- @return boolean or (boolean, string): true on success,
313 -- or nil and an error message on failure.
314 local function recursive_delete(name)
315 local mode = lfs.attributes(name, "mode")
316
317 if mode == "file" then
318 return os.remove(name)
319 elseif mode == "directory" then
320 for file in lfs.dir(name) do
321 if file ~= "." and file ~= ".." then
322 local ok, err = recursive_delete(dir.path(name, file))
323 if not ok then return nil, err end
324 end
325 end
326 local ok, err = lfs.rmdir(name)
327 if not ok then return nil, err end
328 end
329 return true
330 end
331
332 --- Delete a file or a directory and all its contents.
333 -- @param name string: Pathname of source
334 -- @return boolean: true on success, false on failure.
335 function delete(name)
336 name = dir.normalize(name)
337 return recursive_delete(name) or false
338 end
339
340 --- List the contents of a directory.
341 -- @param at string or nil: directory to list (will be the current
342 -- directory if none is given).
343 -- @return table: an array of strings with the filenames representing
344 -- the contents of a directory.
345 function list_dir(at)
346 assert(type(at) == "string" or not at)
347 if not at then
348 at = fs.current_dir()
349 end
350 at = dir.normalize(at)
351 if not fs.is_dir(at) then
352 return {}
353 end
354 local result = {}
355 for file in lfs.dir(at) do
356 if file ~= "." and file ~= ".." then
357 table.insert(result, file)
358 end
359 end
360 return result
361 end
362
363 --- Implementation function for recursive find.
364 -- Assumes paths are normalized.
365 -- @param cwd string: Current working directory in recursion.
366 -- @param prefix string: Auxiliary prefix string to form pathname.
367 -- @param result table: Array of strings where results are collected.
368 local function recursive_find(cwd, prefix, result)
369 for file in lfs.dir(cwd) do
370 if file ~= "." and file ~= ".." then
371 local item = prefix .. file
372 table.insert(result, item)
373 local pathname = dir.path(cwd, file)
374 if lfs.attributes(pathname, "mode") == "directory" then
375 recursive_find(pathname, item..dir_separator, result)
376 end
377 end
378 end
379 end
380
381 --- Recursively scan the contents of a directory.
382 -- @param at string or nil: directory to scan (will be the current
383 -- directory if none is given).
384 -- @return table: an array of strings with the filenames representing
385 -- the contents of a directory.
386 function find(at)
387 assert(type(at) == "string" or not at)
388 if not at then
389 at = fs.current_dir()
390 end
391 at = dir.normalize(at)
392 if not fs.is_dir(at) then
393 return {}
394 end
395 local result = {}
396 recursive_find(at, "", result)
397 return result
398 end
399
400 --- Test for existance of a file.
401 -- @param file string: filename to test
402 -- @return boolean: true if file exists, false otherwise.
403 function exists(file)
404 assert(file)
405 file = dir.normalize(file)
406 return type(lfs.attributes(file)) == "table"
407 end
408
409 --- Test is pathname is a directory.
410 -- @param file string: pathname to test
411 -- @return boolean: true if it is a directory, false otherwise.
412 function is_dir(file)
413 assert(file)
414 file = dir.normalize(file)
415 return lfs.attributes(file, "mode") == "directory"
416 end
417
418 --- Test is pathname is a regular file.
419 -- @param file string: pathname to test
420 -- @return boolean: true if it is a file, false otherwise.
421 function is_file(file)
422 assert(file)
423 file = dir.normalize(file)
424 return lfs.attributes(file, "mode") == "file"
425 end
426
427 function set_time(file, time)
428 file = dir.normalize(file)
429 return lfs.touch(file, time)
430 end
431
432 end
433
434 ---------------------------------------------------------------------
435 -- LuaZip functions
436 ---------------------------------------------------------------------
437
438 if zip_ok then
439
440 function zip(zipfile, ...)
441 return lrzip.zip(zipfile, ...)
442 end
443
444 end
445
446 if unzip_ok then
447 --- Uncompress files from a .zip archive.
448 -- @param zipfile string: pathname of .zip archive to be extracted.
449 -- @return boolean: true on success, false on failure.
450 function unzip(zipfile)
451 local zipfile, err = luazip.open(zipfile)
452 if not zipfile then return nil, err end
453 local files = zipfile:files()
454 local file = files()
455 repeat
456 if file.filename:sub(#file.filename) == "/" then
457 fs.make_dir(dir.path(fs.current_dir(), file.filename))
458 else
459 local rf, err = zipfile:open(file.filename)
460 if not rf then zipfile:close(); return nil, err end
461 local contents = rf:read("*a")
462 rf:close()
463 local wf, err = io.open(dir.path(fs.current_dir(), file.filename), "wb")
464 if not wf then zipfile:close(); return nil, err end
465 wf:write(contents)
466 wf:close()
467 end
468 file = files()
469 until not file
470 zipfile:close()
471 return true
472 end
473
474 end
475
476 ---------------------------------------------------------------------
477 -- LuaSocket functions
478 ---------------------------------------------------------------------
479
480 if socket_ok then
481
482 local ltn12 = require("ltn12")
483 local luasec_ok, https = pcall(require, "ssl.https")
484 local redirect_protocols = {
485 http = http,
486 https = luasec_ok and https,
487 }
488
489 local function http_request(url, http, loop_control)
490 local result = {}
491
492 local proxy = cfg.proxy
493 if type(proxy) ~= "string" then proxy = nil end
494 -- LuaSocket's http.request crashes when given URLs missing the scheme part.
495 if proxy and not proxy:find("://") then
496 proxy = "http://" .. proxy
497 end
498
499 local res, status, headers, err = http.request {
500 url = url,
501 proxy = proxy,
502 redirect = false,
503 sink = ltn12.sink.table(result),
504 headers = {
505 ["user-agent"] = cfg.user_agent.." via LuaSocket"
506 },
507 }
508 if not res then
509 return nil, status
510 elseif status == 301 or status == 302 then
511 local location = headers.location
512 if location then
513 local protocol, rest = dir.split_url(location)
514 if redirect_protocols[protocol] then
515 if not loop_control then
516 loop_control = {}
517 elseif loop_control[location] then
518 return nil, "Redirection loop -- broken URL?"
519 end
520 loop_control[url] = true
521 return http_request(location, redirect_protocols[protocol], loop_control)
522 else
523 return nil, "URL redirected to unsupported protocol - install luasec to get HTTPS support."
524 end
525 end
526 return nil, err
527 elseif status ~= 200 then
528 return nil, err
529 else
530 return table.concat(result)
531 end
532 end
533
534 --- Download a remote file.
535 -- @param url string: URL to be fetched.
536 -- @param filename string or nil: this function attempts to detect the
537 -- resulting local filename of the remote file as the basename of the URL;
538 -- if that is not correct (due to a redirection, for example), the local
539 -- filename can be given explicitly as this second argument.
540 -- @return boolean: true on success, false on failure.
541 function download(url, filename)
542 assert(type(url) == "string")
543 assert(type(filename) == "string" or not filename)
544
545 filename = dir.path(fs.current_dir(), filename or dir.base_name(url))
546
547 local content, err
548 if util.starts_with(url, "http:") then
549 content, err = http_request(url, http)
550 elseif util.starts_with(url, "ftp:") then
551 content, err = ftp.get(url)
552 elseif util.starts_with(url, "https:") then
553 if luasec_ok then
554 content, err = http_request(url, https)
555 else
556 err = "Unsupported protocol - install luasec to get HTTPS support."
557 end
558 else
559 err = "Unsupported protocol"
560 end
561 if not content then
562 return false, tostring(err)
563 end
564 local file = io.open(filename, "wb")
565 if not file then return false end
566 file:write(content)
567 file:close()
568 return true
569 end
570
571 end
572 ---------------------------------------------------------------------
573 -- MD5 functions
574 ---------------------------------------------------------------------
575
576 if md5_ok then
577
578 --- Get the MD5 checksum for a file.
579 -- @param file string: The file to be computed.
580 -- @return string: The MD5 checksum
581 function get_md5(file)
582 file = fs.absolute_name(file)
583 local file = io.open(file, "rb")
584 if not file then return false end
585 local computed = md5.sumhexa(file:read("*a"))
586 file:close()
587 return computed
588 end
589
590 end
591
592 ---------------------------------------------------------------------
593 -- POSIX functions
594 ---------------------------------------------------------------------
595
596 if posix_ok then
597
598 local octal_to_rwx = {
599 ["0"] = "---",
600 ["1"] = "--x",
601 ["2"] = "-w-",
602 ["3"] = "-wx",
603 ["4"] = "r--",
604 ["5"] = "r-x",
605 ["6"] = "rw-",
606 ["7"] = "rwx",
607 }
608
609 function chmod(file, mode)
610 -- LuaPosix (as of 5.1.15) does not support octal notation...
611 if mode:sub(1,1) == "0" then
612 local new_mode = {}
613 for c in mode:sub(2):gmatch(".") do
614 table.insert(new_mode, octal_to_rwx[c])
615 end
616 mode = table.concat(new_mode)
617 end
618 local err = posix.chmod(file, mode)
619 return err == 0
620 end
621
622 function get_permissions(file)
623 return posix.stat(file, "mode")
624 end
625
626 end
627
628 ---------------------------------------------------------------------
629 -- Other functions
630 ---------------------------------------------------------------------
631
632 --- Apply a patch.
633 -- @param patchname string: The filename of the patch.
634 -- @param patchdata string or nil: The actual patch as a string.
635 function apply_patch(patchname, patchdata)
636 local p, all_ok = patch.read_patch(patchname, patchdata)
637 if not all_ok then
638 return nil, "Failed reading patch "..patchname
639 end
640 if p then
641 return patch.apply_patch(p, 1)
642 end
643 end
644
645 --- Move a file.
646 -- @param src string: Pathname of source
647 -- @param dest string: Pathname of destination
648 -- @return boolean or (boolean, string): true on success, false on failure,
649 -- plus an error message.
650 function move(src, dest)
651 assert(src and dest)
652 if fs.exists(dest) and not fs.is_dir(dest) then
653 return false, "File already exists: "..dest
654 end
655 local ok, err = fs.copy(src, dest)
656 if not ok then
657 return false, err
658 end
659 ok = fs.delete(src)
660 if not ok then
661 return false, "Failed move: could not delete "..src.." after copy."
662 end
663 return true
664 end
665
666 --- Check if user has write permissions for the command.
667 -- Assumes the configuration variables under cfg have been previously set up.
668 -- @param flags table: the flags table passed to run() drivers.
669 -- @return boolean or (boolean, string): true on success, false on failure,
670 -- plus an error message.
671 function check_command_permissions(flags)
672 local root_dir = path.root_dir(cfg.rocks_dir)
673 local ok = true
674 local err = ""
675 for _, dir in ipairs { cfg.rocks_dir, root_dir } do
676 if fs.exists(dir) and not fs.is_writable(dir) then
677 ok = false
678 err = "Your user does not have write permissions in " .. dir
679 break
680 end
681 end
682 local root_parent = dir.dir_name(root_dir)
683 if ok and not fs.exists(root_dir) and not fs.is_writable(root_parent) then
684 ok = false
685 err = root_dir.." does not exist and your user does not have write permissions in " .. root_parent
686 end
687 if ok then
688 return true
689 else
690 if flags["local"] then
691 err = err .. " \n-- please check your permissions."
692 else
693 err = err .. " \n-- you may want to run as a privileged user or use your local tree with --local."
694 end
695 return nil, err
696 end
697 end