Mercurial > repo
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 |