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 /src | |
| parent | d81020338c99ff4116431ee496a7db516eb91f00 (diff) | |
| download | luarocks-89daf1f1588189f0fedc0b050408d785729a6833.tar.gz luarocks-89daf1f1588189f0fedc0b050408d785729a6833.tar.bz2 luarocks-89daf1f1588189f0fedc0b050408d785729a6833.zip | |
Introduce locking for concurrent access control (#1557)
Fixes #1540
Diffstat (limited to 'src')
| -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 |
