1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
|
--- fs operations implemented with third-party tools for Unix platform abstractions.
local tools = {}
local fs = require("luarocks.fs")
local dir = require("luarocks.dir")
local cfg = require("luarocks.core.cfg")
local vars = setmetatable({}, { __index = function(_,k) return cfg.variables[k] end })
--- Adds prefix to command to make it run from a directory.
-- @param directory string: Path to a directory.
-- @param cmd string: A command-line string.
-- @return string: The command-line with prefix.
function tools.command_at(directory, cmd)
return "cd " .. fs.Q(fs.absolute_name(directory)) .. " && " .. cmd
end
--- Create a directory if it does not already exist.
-- If any of the higher levels in the path name does not exist
-- too, they are created as well.
-- @param directory string: pathname of directory to create.
-- @return boolean: true on success, false on failure.
function tools.make_dir(directory)
assert(directory)
local ok, err = fs.execute(vars.MKDIR.." -p", directory)
if not ok then
err = "failed making directory "..directory
end
return ok, err
end
--- Remove a directory if it is empty.
-- Does not return errors (for example, if directory is not empty or
-- if already does not exist)
-- @param directory string: pathname of directory to remove.
function tools.remove_dir_if_empty(directory)
assert(directory)
fs.execute_quiet(vars.RMDIR, directory)
end
--- Remove a directory if it is empty.
-- Does not return errors (for example, if directory is not empty or
-- if already does not exist)
-- @param directory string: pathname of directory to remove.
function tools.remove_dir_tree_if_empty(directory)
assert(directory)
fs.execute_quiet(vars.RMDIR, "-p", directory)
end
--- Copy a file.
-- @param src string: Pathname of source
-- @param dest string: Pathname of destination
-- @param perm string ("read" or "exec") or nil: Permissions for destination
-- file or nil to use the source permissions
-- @return boolean or (boolean, string): true on success, false on failure,
-- plus an error message.
function tools.copy(src, dest, perm)
assert(src and dest)
if fs.execute(vars.CP, src, dest) then
if perm then
if fs.is_dir(dest) then
dest = dir.path(dest, dir.base_name(src))
end
if fs.set_permissions(dest, perm, "all") then
return true
else
return false, "Failed setting permissions of "..dest
end
end
return true
else
return false, "Failed copying "..src.." to "..dest
end
end
--- Recursively copy the contents of a directory.
-- @param src string: Pathname of source
-- @param dest string: Pathname of destination
-- @return boolean or (boolean, string): true on success, false on failure,
-- plus an error message.
function tools.copy_contents(src, dest)
assert(src and dest)
if not fs.is_dir(src) then
return false, src .. " is not a directory"
end
if fs.make_dir(dest) and fs.execute_quiet(vars.CP.." -pPR "..fs.Q(src).."/* "..fs.Q(dest)) then
return true
else
return false, "Failed copying "..src.." to "..dest
end
end
--- Delete a file or a directory and all its contents.
-- For safety, this only accepts absolute paths.
-- @param arg string: Pathname of source
-- @return nil
function tools.delete(arg)
assert(arg)
assert(arg:sub(1,1) == "/")
fs.execute_quiet(vars.RM, "-rf", arg)
end
--- Recursively scan the contents of a directory.
-- @param at string or nil: directory to scan (will be the current
-- directory if none is given).
-- @return table: an array of strings with the filenames representing
-- the contents of a directory.
function tools.find(at)
assert(type(at) == "string" or not at)
if not at then
at = fs.current_dir()
end
if not fs.is_dir(at) then
return {}
end
local result = {}
local pipe = io.popen(fs.command_at(at, fs.quiet_stderr(vars.FIND.." *")))
for file in pipe:lines() do
table.insert(result, file)
end
pipe:close()
return result
end
--- Compress files in a .zip archive.
-- @param zipfile string: pathname of .zip archive to be created.
-- @param ... Filenames to be stored in the archive are given as
-- additional arguments.
-- @return boolean: true on success, nil and error message on failure.
function tools.zip(zipfile, ...)
local ok, err = fs.is_tool_available(vars.ZIP, "zip")
if not ok then
return nil, err
end
if fs.execute_quiet(vars.ZIP.." -r", zipfile, ...) then
return true
else
return nil, "failed compressing " .. zipfile
end
end
--- Uncompress files from a .zip archive.
-- @param zipfile string: pathname of .zip archive to be extracted.
-- @return boolean: true on success, nil and error message on failure.
function tools.unzip(zipfile)
assert(zipfile)
local ok, err = fs.is_tool_available(vars.UNZIP, "unzip")
if not ok then
return nil, err
end
if fs.execute_quiet(vars.UNZIP, zipfile) then
return true
else
return nil, "failed extracting " .. zipfile
end
end
local function uncompress(default_ext, program, infile, outfile)
assert(type(infile) == "string")
assert(outfile == nil or type(outfile) == "string")
if not outfile then
outfile = infile:gsub("%."..default_ext.."$", "")
end
if fs.execute(fs.Q(program).." -c "..fs.Q(infile).." > "..fs.Q(outfile)) then
return true
else
return nil, "failed extracting " .. infile
end
end
--- Uncompresses a .gz file.
-- @param infile string: pathname of .gz file to be extracted.
-- @param outfile string or nil: pathname of output file to be produced.
-- If not given, name is derived from input file.
-- @return boolean: true on success; nil and error message on failure.
function tools.gunzip(infile, outfile)
return uncompress("gz", "gunzip", infile, outfile)
end
--- Uncompresses a .bz2 file.
-- @param infile string: pathname of .bz2 file to be extracted.
-- @param outfile string or nil: pathname of output file to be produced.
-- If not given, name is derived from input file.
-- @return boolean: true on success; nil and error message on failure.
function tools.bunzip2(infile, outfile)
return uncompress("bz2", "bunzip2", infile, outfile)
end
do
local function rwx_to_octal(rwx)
return (rwx:match "r" and 4 or 0)
+ (rwx:match "w" and 2 or 0)
+ (rwx:match "x" and 1 or 0)
end
local umask_cache
function tools._unix_umask()
if umask_cache then
return umask_cache
end
local fd = assert(io.popen("umask -S"))
local umask = assert(fd:read("*a"))
fd:close()
local u, g, o = umask:match("u=([rwx]*),g=([rwx]*),o=([rwx]*)")
if not u then
error("invalid umask result")
end
umask_cache = string.format("%d%d%d",
7 - rwx_to_octal(u),
7 - rwx_to_octal(g),
7 - rwx_to_octal(o))
return umask_cache
end
end
--- Set permissions for file or directory
-- @param filename string: filename whose permissions are to be modified
-- @param mode string ("read" or "exec"): permissions to set
-- @param scope string ("user" or "all"): the user(s) to whom the permission applies
-- @return boolean or (boolean, string): true on success, false on failure,
-- plus an error message
function tools.set_permissions(filename, mode, scope)
assert(filename and mode and scope)
local perms, err = fs._unix_mode_scope_to_perms(mode, scope)
if err then
return false, err
end
return fs.execute(vars.CHMOD, perms, filename)
end
function tools.browser(url)
return fs.execute(cfg.web_browser, url)
end
-- Set access and modification times for a file.
-- @param filename File to set access and modification times for.
-- @param time may be a string or number containing the format returned
-- by os.time, or a table ready to be processed via os.time; if
-- nil, current time is assumed.
function tools.set_time(file, time)
assert(time == nil or type(time) == "table" or type(time) == "number")
file = dir.normalize(file)
local flag = ""
if type(time) == "number" then
time = os.date("*t", time)
end
if type(time) == "table" then
flag = ("-t %04d%02d%02d%02d%02d.%02d"):format(time.year, time.month, time.day, time.hour, time.min, time.sec)
end
return fs.execute(vars.TOUCH .. " " .. flag, file)
end
--- Create a temporary directory.
-- @param name_pattern string: name pattern to use for avoiding conflicts
-- when creating temporary directory.
-- @return string or (nil, string): name of temporary directory or (nil, error message) on failure.
function tools.make_temp_dir(name_pattern)
assert(type(name_pattern) == "string")
name_pattern = dir.normalize(name_pattern)
local template = (os.getenv("TMPDIR") or "/tmp") .. "/luarocks_" .. name_pattern:gsub("/", "_") .. "-XXXXXX"
local pipe = io.popen(vars.MKTEMP.." -d "..fs.Q(template))
local dirname = pipe:read("*l")
pipe:close()
if dirname and dirname:match("^/") then
return dirname
end
return nil, "Failed to create temporary directory "..tostring(dirname)
end
--- Test is file/directory exists
-- @param file string: filename to test
-- @return boolean: true if file exists, false otherwise.
function tools.exists(file)
assert(file)
return fs.execute(vars.TEST, "-e", file)
end
--- Test is pathname is a directory.
-- @param file string: pathname to test
-- @return boolean: true if it is a directory, false otherwise.
function tools.is_dir(file)
assert(file)
return fs.execute(vars.TEST, "-d", file)
end
--- Test is pathname is a regular file.
-- @param file string: pathname to test
-- @return boolean: true if it is a regular file, false otherwise.
function tools.is_file(file)
assert(file)
return fs.execute(vars.TEST, "-f", file)
end
function tools.current_user()
local user = os.getenv("USER")
if user then
return user
end
local pd = io.popen("whoami", "r")
if not pd then
return ""
end
user = pd:read("*l")
pd:close()
return user
end
function tools.is_superuser()
return fs.current_user() == "root"
end
function tools.lock_access(dirname, force)
local ok, err = fs.make_dir(dirname)
if not ok then
return nil, err
end
local tempfile = dir.path(dirname, ".lock.tmp." .. tostring(math.random(100000000)))
local fd, fderr = io.open(tempfile, "w")
if not fd then
return nil, "failed opening temp file " .. tempfile .. " for locking: " .. fderr
end
local ok, werr = fd:write("lock file for " .. dirname)
if not ok then
return nil, "failed writing temp file " .. tempfile .. " for locking: " .. werr
end
fd:close()
local lockfile = dir.path(dirname, "lockfile.lfs")
local force_flag = force and " -f" or ""
if fs.execute(vars.LN .. force_flag, tempfile, lockfile) then
return {
tempfile = tempfile,
lockfile = lockfile,
}
else
return nil, "File exists" -- same message as luafilesystem
end
end
function tools.unlock_access(lock)
os.remove(lock.lockfile)
os.remove(lock.tempfile)
end
return tools
|