diff options
author | Hisham Muhammad <hisham@gobolinux.org> | 2023-12-13 14:31:02 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-12-13 19:31:02 -0300 |
commit | 89daf1f1588189f0fedc0b050408d785729a6833 (patch) | |
tree | ebf84abf3f1981630cddf26af6bf3253109b8798 | |
parent | d81020338c99ff4116431ee496a7db516eb91f00 (diff) | |
download | luarocks-89daf1f1588189f0fedc0b050408d785729a6833.tar.gz luarocks-89daf1f1588189f0fedc0b050408d785729a6833.tar.bz2 luarocks-89daf1f1588189f0fedc0b050408d785729a6833.zip |
Introduce locking for concurrent access control (#1557)
Fixes #1540
-rw-r--r-- | src/luarocks/cmd.lua | 21 | ||||
-rw-r--r-- | src/luarocks/cmd/build.lua | 2 | ||||
-rw-r--r-- | src/luarocks/cmd/init.lua | 2 | ||||
-rw-r--r-- | src/luarocks/cmd/install.lua | 2 | ||||
-rw-r--r-- | src/luarocks/cmd/make.lua | 2 | ||||
-rw-r--r-- | src/luarocks/cmd/purge.lua | 12 | ||||
-rw-r--r-- | src/luarocks/cmd/remove.lua | 2 | ||||
-rw-r--r-- | src/luarocks/core/cfg.lua | 1 | ||||
-rw-r--r-- | src/luarocks/fetch.lua | 6 | ||||
-rw-r--r-- | src/luarocks/fs/lua.lua | 12 | ||||
-rw-r--r-- | src/luarocks/fs/unix/tools.lua | 39 | ||||
-rw-r--r-- | src/luarocks/fs/win32/tools.lua | 9 |
12 files changed, 100 insertions, 10 deletions
diff --git a/src/luarocks/cmd.lua b/src/luarocks/cmd.lua index ca79c316..2db0c039 100644 --- a/src/luarocks/cmd.lua +++ b/src/luarocks/cmd.lua | |||
@@ -26,6 +26,7 @@ cmd.errorcodes = { | |||
26 | UNSPECIFIED = 1, | 26 | UNSPECIFIED = 1, |
27 | PERMISSIONDENIED = 2, | 27 | PERMISSIONDENIED = 2, |
28 | CONFIGFILE = 3, | 28 | CONFIGFILE = 3, |
29 | LOCK = 4, | ||
29 | CRASH = 99 | 30 | CRASH = 99 |
30 | } | 31 | } |
31 | 32 | ||
@@ -477,6 +478,8 @@ Enabling completion for Fish: | |||
477 | "To enable it, see '"..program.." help path'.") | 478 | "To enable it, see '"..program.." help path'.") |
478 | parser:flag("--global", "Use the system tree when `local_by_default` is `true`.") | 479 | parser:flag("--global", "Use the system tree when `local_by_default` is `true`.") |
479 | parser:flag("--no-project", "Do not use project tree even if running from a project folder.") | 480 | parser:flag("--no-project", "Do not use project tree even if running from a project folder.") |
481 | parser:flag("--force-lock", "Attempt to overwrite the lock for commands " .. | ||
482 | "that require exclusive access, such as 'install'") | ||
480 | parser:flag("--verbose", "Display verbose output of commands executed.") | 483 | parser:flag("--verbose", "Display verbose output of commands executed.") |
481 | parser:option("--timeout", "Timeout on network operations, in seconds.\n".. | 484 | parser:option("--timeout", "Timeout on network operations, in seconds.\n".. |
482 | "0 means no timeout (wait forever). Default is ".. | 485 | "0 means no timeout (wait forever). Default is ".. |
@@ -672,10 +675,28 @@ function cmd.run_command(description, commands, external_namespace, ...) | |||
672 | end | 675 | end |
673 | 676 | ||
674 | local cmd_mod = cmd_modules[args.command] | 677 | local cmd_mod = cmd_modules[args.command] |
678 | |||
679 | local lock | ||
680 | if cmd_mod.needs_lock then | ||
681 | lock, err = fs.lock_access(path.root_dir(cfg.root_dir), args.force_lock) | ||
682 | if not lock then | ||
683 | local try_force = args.force_lock | ||
684 | and (" - failed to force the lock" .. (err and ": " .. err or "")) | ||
685 | or " - use --force-lock to overwrite the lock" | ||
686 | die("command '" .. args.command .. "' " .. | ||
687 | "requires exclusive access to " .. cfg.root_dir .. | ||
688 | try_force, cmd.errorcodes.LOCK) | ||
689 | end | ||
690 | end | ||
691 | |||
675 | local call_ok, ok, err, exitcode = xpcall(function() | 692 | local call_ok, ok, err, exitcode = xpcall(function() |
676 | return cmd_mod.command(args) | 693 | return cmd_mod.command(args) |
677 | end, error_handler) | 694 | end, error_handler) |
678 | 695 | ||
696 | if lock then | ||
697 | fs.unlock_access(lock) | ||
698 | end | ||
699 | |||
679 | if not call_ok then | 700 | if not call_ok then |
680 | die(ok, cmd.errorcodes.CRASH) | 701 | die(ok, cmd.errorcodes.CRASH) |
681 | elseif not ok then | 702 | elseif not ok then |
diff --git a/src/luarocks/cmd/build.lua b/src/luarocks/cmd/build.lua index 56f0e757..16c0aff2 100644 --- a/src/luarocks/cmd/build.lua +++ b/src/luarocks/cmd/build.lua | |||
@@ -180,4 +180,6 @@ function cmd_build.command(args) | |||
180 | return name, version | 180 | return name, version |
181 | end | 181 | end |
182 | 182 | ||
183 | cmd_build.needs_lock = true | ||
184 | |||
183 | return cmd_build | 185 | return cmd_build |
diff --git a/src/luarocks/cmd/init.lua b/src/luarocks/cmd/init.lua index 8bccb23f..dcd6b759 100644 --- a/src/luarocks/cmd/init.lua +++ b/src/luarocks/cmd/init.lua | |||
@@ -173,4 +173,6 @@ function init.command(args) | |||
173 | return true | 173 | return true |
174 | end | 174 | end |
175 | 175 | ||
176 | init.needs_lock = true | ||
177 | |||
176 | return init | 178 | return init |
diff --git a/src/luarocks/cmd/install.lua b/src/luarocks/cmd/install.lua index c2e947b2..b4f15cb8 100644 --- a/src/luarocks/cmd/install.lua +++ b/src/luarocks/cmd/install.lua | |||
@@ -255,4 +255,6 @@ function install.command(args) | |||
255 | end | 255 | end |
256 | end | 256 | end |
257 | 257 | ||
258 | install.needs_lock = true | ||
259 | |||
258 | return install | 260 | return install |
diff --git a/src/luarocks/cmd/make.lua b/src/luarocks/cmd/make.lua index 8b313bb9..8147b5df 100644 --- a/src/luarocks/cmd/make.lua +++ b/src/luarocks/cmd/make.lua | |||
@@ -155,4 +155,6 @@ function make.command(args) | |||
155 | end | 155 | end |
156 | end | 156 | end |
157 | 157 | ||
158 | make.needs_lock = true | ||
159 | |||
158 | return make | 160 | return make |
diff --git a/src/luarocks/cmd/purge.lua b/src/luarocks/cmd/purge.lua index c300e286..09380ef1 100644 --- a/src/luarocks/cmd/purge.lua +++ b/src/luarocks/cmd/purge.lua | |||
@@ -39,18 +39,10 @@ end | |||
39 | function purge.command(args) | 39 | function purge.command(args) |
40 | local tree = args.tree | 40 | local tree = args.tree |
41 | 41 | ||
42 | if type(tree) ~= "string" then | ||
43 | return nil, "The --tree argument is mandatory. "..util.see_help("purge") | ||
44 | end | ||
45 | |||
46 | local results = {} | ||
47 | if not fs.is_dir(tree) then | ||
48 | return nil, "Directory not found: "..tree | ||
49 | end | ||
50 | |||
51 | local ok, err = fs.check_command_permissions(args) | 42 | local ok, err = fs.check_command_permissions(args) |
52 | if not ok then return nil, err, cmd.errorcodes.PERMISSIONDENIED end | 43 | if not ok then return nil, err, cmd.errorcodes.PERMISSIONDENIED end |
53 | 44 | ||
45 | local results = {} | ||
54 | search.local_manifest_search(results, path.rocks_dir(tree), queries.all()) | 46 | search.local_manifest_search(results, path.rocks_dir(tree), queries.all()) |
55 | 47 | ||
56 | local sort = function(a,b) return vers.compare_versions(b,a) end | 48 | local sort = function(a,b) return vers.compare_versions(b,a) end |
@@ -81,4 +73,6 @@ function purge.command(args) | |||
81 | return writer.make_manifest(cfg.rocks_dir, "one") | 73 | return writer.make_manifest(cfg.rocks_dir, "one") |
82 | end | 74 | end |
83 | 75 | ||
76 | purge.needs_lock = true | ||
77 | |||
84 | return purge | 78 | return purge |
diff --git a/src/luarocks/cmd/remove.lua b/src/luarocks/cmd/remove.lua index 6bb69ad6..affeda25 100644 --- a/src/luarocks/cmd/remove.lua +++ b/src/luarocks/cmd/remove.lua | |||
@@ -72,4 +72,6 @@ function cmd_remove.command(args) | |||
72 | return true | 72 | return true |
73 | end | 73 | end |
74 | 74 | ||
75 | cmd_remove.needs_lock = true | ||
76 | |||
75 | return cmd_remove | 77 | return cmd_remove |
diff --git a/src/luarocks/core/cfg.lua b/src/luarocks/core/cfg.lua index 9c2728cc..81987cc9 100644 --- a/src/luarocks/core/cfg.lua +++ b/src/luarocks/core/cfg.lua | |||
@@ -238,6 +238,7 @@ local function make_defaults(lua_version, target_cpu, platforms, home) | |||
238 | MKDIR = "mkdir", | 238 | MKDIR = "mkdir", |
239 | RMDIR = "rmdir", | 239 | RMDIR = "rmdir", |
240 | CP = "cp", | 240 | CP = "cp", |
241 | LN = "ln", | ||
241 | LS = "ls", | 242 | LS = "ls", |
242 | RM = "rm", | 243 | RM = "rm", |
243 | FIND = "find", | 244 | FIND = "find", |
diff --git a/src/luarocks/fetch.lua b/src/luarocks/fetch.lua index 723af8b5..09f7a40b 100644 --- a/src/luarocks/fetch.lua +++ b/src/luarocks/fetch.lua | |||
@@ -33,21 +33,25 @@ function fetch.fetch_caching(url, mirroring) | |||
33 | local name = repo_url:gsub("[/:]","_") | 33 | local name = repo_url:gsub("[/:]","_") |
34 | local cache_dir = dir.path(cfg.local_cache, name) | 34 | local cache_dir = dir.path(cfg.local_cache, name) |
35 | local ok = fs.make_dir(cache_dir) | 35 | local ok = fs.make_dir(cache_dir) |
36 | if not ok then | 36 | local lock = ok and fs.lock_access(cache_dir) |
37 | if not (ok and lock) then | ||
37 | cfg.local_cache = fs.make_temp_dir("local_cache") | 38 | cfg.local_cache = fs.make_temp_dir("local_cache") |
38 | cache_dir = dir.path(cfg.local_cache, name) | 39 | cache_dir = dir.path(cfg.local_cache, name) |
39 | ok = fs.make_dir(cache_dir) | 40 | ok = fs.make_dir(cache_dir) |
40 | if not ok then | 41 | if not ok then |
41 | return nil, "Failed creating temporary cache directory "..cache_dir | 42 | return nil, "Failed creating temporary cache directory "..cache_dir |
42 | end | 43 | end |
44 | lock = fs.lock_access(cache_dir) | ||
43 | end | 45 | end |
44 | 46 | ||
45 | local cachefile = dir.path(cache_dir, filename) | 47 | local cachefile = dir.path(cache_dir, filename) |
46 | if cfg.aggressive_cache and (not name:match("^manifest")) and fs.exists(cachefile) then | 48 | if cfg.aggressive_cache and (not name:match("^manifest")) and fs.exists(cachefile) then |
49 | fs.unlock_access(lock) | ||
47 | return cachefile, nil, nil, true | 50 | return cachefile, nil, nil, true |
48 | end | 51 | end |
49 | 52 | ||
50 | local file, err, errcode, from_cache = fetch.fetch_url(url, cachefile, true, mirroring) | 53 | local file, err, errcode, from_cache = fetch.fetch_url(url, cachefile, true, mirroring) |
54 | fs.unlock_access(lock) | ||
51 | if not file then | 55 | if not file then |
52 | return nil, err or "Failed downloading "..url, errcode | 56 | return nil, err or "Failed downloading "..url, errcode |
53 | end | 57 | end |
diff --git a/src/luarocks/fs/lua.lua b/src/luarocks/fs/lua.lua index 174fdc88..ec497f88 100644 --- a/src/luarocks/fs/lua.lua +++ b/src/luarocks/fs/lua.lua | |||
@@ -283,6 +283,18 @@ end | |||
283 | 283 | ||
284 | if lfs_ok then | 284 | if lfs_ok then |
285 | 285 | ||
286 | function fs_lua.lock_access(dirname, force) | ||
287 | fs.make_dir(dirname) | ||
288 | if force then | ||
289 | os.remove(dir.path(dirname, "lockfile.lfs")) | ||
290 | end | ||
291 | return lfs.lock_dir(dirname) | ||
292 | end | ||
293 | |||
294 | function fs_lua.unlock_access(lock) | ||
295 | return lock:free() | ||
296 | end | ||
297 | |||
286 | --- Run the given command. | 298 | --- Run the given command. |
287 | -- The command is executed in the current directory in the dir stack. | 299 | -- The command is executed in the current directory in the dir stack. |
288 | -- @param cmd string: No quoting/escaping is applied to the command. | 300 | -- @param cmd string: No quoting/escaping is applied to the command. |
diff --git a/src/luarocks/fs/unix/tools.lua b/src/luarocks/fs/unix/tools.lua index b7fd4de5..7106ed81 100644 --- a/src/luarocks/fs/unix/tools.lua +++ b/src/luarocks/fs/unix/tools.lua | |||
@@ -315,4 +315,43 @@ function tools.is_superuser() | |||
315 | return fs.current_user() == "root" | 315 | return fs.current_user() == "root" |
316 | end | 316 | end |
317 | 317 | ||
318 | function tools.lock_access(dirname, force) | ||
319 | fs.make_dir(dirname) | ||
320 | |||
321 | local tempfile = os.tmpname() | ||
322 | if not tempfile then | ||
323 | return nil, "failed creating temp file for locking" | ||
324 | end | ||
325 | |||
326 | local fd, fderr = io.open(tempfile, "w") | ||
327 | if not fd then | ||
328 | return nil, "failed opening temp file " .. tempfile .. " for locking: " .. fderr | ||
329 | end | ||
330 | |||
331 | local ok, werr = fd:write("lock file for " .. dirname) | ||
332 | if not ok then | ||
333 | return nil, "failed writing temp file " .. tempfile .. " for locking: " .. werr | ||
334 | end | ||
335 | |||
336 | fd:close() | ||
337 | |||
338 | local lockfile = dir.path(dirname, "lockfile.luarocks") | ||
339 | |||
340 | local force_flag = force and " -f" or "" | ||
341 | |||
342 | if fs.execute(vars.LN .. force_flag, tempfile, lockfile) then | ||
343 | return { | ||
344 | tempfile = tempfile, | ||
345 | lockfile = lockfile, | ||
346 | } | ||
347 | else | ||
348 | return nil, "File exists" -- same message as luafilesystem | ||
349 | end | ||
350 | end | ||
351 | |||
352 | function tools.unlock_access(lock) | ||
353 | os.remove(lock.lockfile) | ||
354 | os.remove(lock.tempfile) | ||
355 | end | ||
356 | |||
318 | return tools | 357 | return tools |
diff --git a/src/luarocks/fs/win32/tools.lua b/src/luarocks/fs/win32/tools.lua index 9bd050c6..be63063b 100644 --- a/src/luarocks/fs/win32/tools.lua +++ b/src/luarocks/fs/win32/tools.lua | |||
@@ -316,4 +316,13 @@ function tools.set_time(filename, time) | |||
316 | return true -- FIXME | 316 | return true -- FIXME |
317 | end | 317 | end |
318 | 318 | ||
319 | function tools.lock_access(dirname) | ||
320 | -- NYI | ||
321 | return {} | ||
322 | end | ||
323 | |||
324 | function tools.unlock_access(lock) | ||
325 | -- NYI | ||
326 | end | ||
327 | |||
319 | return tools | 328 | return tools |