diff --git a/src/cargo/util/flock.rs b/src/cargo/util/flock.rs index 3df204df084..a818a0b15a1 100644 --- a/src/cargo/util/flock.rs +++ b/src/cargo/util/flock.rs @@ -7,6 +7,7 @@ //! The [`FileLock`] type represents a locked file, and provides access to the //! file. +use std::fs::TryLockError; use std::fs::{File, OpenOptions}; use std::io; use std::io::{Read, Seek, SeekFrom, Write}; @@ -17,7 +18,6 @@ use crate::util::errors::CargoResult; use crate::util::style; use anyhow::Context as _; use cargo_util::paths; -use sys::*; /// A locked file. /// @@ -103,7 +103,7 @@ impl Write for FileLock { impl Drop for FileLock { fn drop(&mut self) { if let Some(f) = self.f.take() { - if let Err(e) = unlock(&f) { + if let Err(e) = f.unlock() { tracing::warn!("failed to release lock: {e:?}"); } } @@ -216,9 +216,7 @@ impl Filesystem { let mut opts = OpenOptions::new(); opts.read(true).write(true).create(true); let (path, f) = self.open(path.as_ref(), &opts, true)?; - acquire(gctx, msg, &path, &|| try_lock_exclusive(&f), &|| { - lock_exclusive(&f) - })?; + acquire(gctx, msg, &path, &|| f.try_lock(), &|| f.lock())?; Ok(FileLock { f: Some(f), path }) } @@ -233,7 +231,7 @@ impl Filesystem { let mut opts = OpenOptions::new(); opts.read(true).write(true).create(true); let (path, f) = self.open(path.as_ref(), &opts, true)?; - if try_acquire(&path, &|| try_lock_exclusive(&f))? { + if try_acquire(&path, &|| f.try_lock())? { Ok(Some(FileLock { f: Some(f), path })) } else { Ok(None) @@ -259,8 +257,8 @@ impl Filesystem { P: AsRef, { let (path, f) = self.open(path.as_ref(), &OpenOptions::new().read(true), false)?; - acquire(gctx, msg, &path, &|| try_lock_shared(&f), &|| { - lock_shared(&f) + acquire(gctx, msg, &path, &|| f.try_lock_shared(), &|| { + f.lock_shared() })?; Ok(FileLock { f: Some(f), path }) } @@ -279,8 +277,8 @@ impl Filesystem { let mut opts = OpenOptions::new(); opts.read(true).write(true).create(true); let (path, f) = self.open(path.as_ref(), &opts, true)?; - acquire(gctx, msg, &path, &|| try_lock_shared(&f), &|| { - lock_shared(&f) + acquire(gctx, msg, &path, &|| f.try_lock_shared(), &|| { + f.lock_shared() })?; Ok(FileLock { f: Some(f), path }) } @@ -296,7 +294,7 @@ impl Filesystem { let mut opts = OpenOptions::new(); opts.read(true).write(true).create(true); let (path, f) = self.open(path.as_ref(), &opts, true)?; - if try_acquire(&path, &|| try_lock_shared(&f))? { + if try_acquire(&path, &|| f.try_lock_shared())? { Ok(Some(FileLock { f: Some(f), path })) } else { Ok(None) @@ -335,7 +333,7 @@ impl PartialEq for Path { } } -fn try_acquire(path: &Path, lock_try: &dyn Fn() -> io::Result<()>) -> CargoResult { +fn try_acquire(path: &Path, lock_try: &dyn Fn() -> Result<(), TryLockError>) -> CargoResult { // File locking on Unix is currently implemented via `flock`, which is known // to be broken on NFS. We could in theory just ignore errors that happen on // NFS, but apparently the failure mode [1] for `flock` on NFS is **blocking @@ -352,22 +350,21 @@ fn try_acquire(path: &Path, lock_try: &dyn Fn() -> io::Result<()>) -> CargoResul } match lock_try() { - Ok(()) => return Ok(true), + Ok(()) => Ok(true), // In addition to ignoring NFS which is commonly not working we also // just ignore locking on filesystems that look like they don't // implement file locking. - Err(e) if error_unsupported(&e) => return Ok(true), + Err(TryLockError::Error(e)) if error_unsupported(&e) => Ok(true), - Err(e) => { - if !error_contended(&e) { - let e = anyhow::Error::from(e); - let cx = format!("failed to lock file: {}", path.display()); - return Err(e.context(cx)); - } + Err(TryLockError::Error(e)) => { + let e = anyhow::Error::from(e); + let cx = format!("failed to lock file: {}", path.display()); + Err(e.context(cx)) } + + Err(TryLockError::WouldBlock) => Ok(false), } - Ok(false) } /// Acquires a lock on a file in a "nice" manner. @@ -389,7 +386,7 @@ fn acquire( gctx: &GlobalContext, msg: &str, path: &Path, - lock_try: &dyn Fn() -> io::Result<()>, + lock_try: &dyn Fn() -> Result<(), TryLockError>, lock_block: &dyn Fn() -> io::Result<()>, ) -> CargoResult<()> { if cfg!(debug_assertions) { @@ -431,176 +428,20 @@ fn is_on_nfs_mount(_path: &Path) -> bool { } #[cfg(unix)] -mod sys { - use std::fs::File; - use std::io::{Error, Result}; - use std::os::unix::io::AsRawFd; - - #[cfg(not(target_os = "solaris"))] - const LOCK_SH: i32 = libc::LOCK_SH; - #[cfg(target_os = "solaris")] - const LOCK_SH: i32 = 1; - #[cfg(not(target_os = "solaris"))] - const LOCK_EX: i32 = libc::LOCK_EX; - #[cfg(target_os = "solaris")] - const LOCK_EX: i32 = 2; - #[cfg(not(target_os = "solaris"))] - const LOCK_NB: i32 = libc::LOCK_NB; - #[cfg(target_os = "solaris")] - const LOCK_NB: i32 = 4; - #[cfg(not(target_os = "solaris"))] - const LOCK_UN: i32 = libc::LOCK_UN; - #[cfg(target_os = "solaris")] - const LOCK_UN: i32 = 8; - - pub(super) fn lock_shared(file: &File) -> Result<()> { - flock(file, LOCK_SH) - } - - pub(super) fn lock_exclusive(file: &File) -> Result<()> { - flock(file, LOCK_EX) - } - - pub(super) fn try_lock_shared(file: &File) -> Result<()> { - flock(file, LOCK_SH | LOCK_NB) - } - - pub(super) fn try_lock_exclusive(file: &File) -> Result<()> { - flock(file, LOCK_EX | LOCK_NB) - } - - pub(super) fn unlock(file: &File) -> Result<()> { - flock(file, LOCK_UN) - } - - pub(super) fn error_contended(err: &Error) -> bool { - err.raw_os_error().map_or(false, |x| x == libc::EWOULDBLOCK) - } - - pub(super) fn error_unsupported(err: &Error) -> bool { - match err.raw_os_error() { - // Unfortunately, depending on the target, these may or may not be the same. - // For targets in which they are the same, the duplicate pattern causes a warning. - #[allow(unreachable_patterns)] - Some(libc::ENOTSUP | libc::EOPNOTSUPP) => true, - Some(libc::ENOSYS) => true, - _ => false, - } - } - - #[cfg(not(target_os = "solaris"))] - fn flock(file: &File, flag: libc::c_int) -> Result<()> { - let ret = unsafe { libc::flock(file.as_raw_fd(), flag) }; - if ret < 0 { - Err(Error::last_os_error()) - } else { - Ok(()) - } - } - - #[cfg(target_os = "solaris")] - fn flock(file: &File, flag: libc::c_int) -> Result<()> { - // Solaris lacks flock(), so try to emulate using fcntl() - let mut flock = libc::flock { - l_type: 0, - l_whence: 0, - l_start: 0, - l_len: 0, - l_sysid: 0, - l_pid: 0, - l_pad: [0, 0, 0, 0], - }; - flock.l_type = if flag & LOCK_UN != 0 { - libc::F_UNLCK - } else if flag & LOCK_EX != 0 { - libc::F_WRLCK - } else if flag & LOCK_SH != 0 { - libc::F_RDLCK - } else { - panic!("unexpected flock() operation") - }; - - let mut cmd = libc::F_SETLKW; - if (flag & LOCK_NB) != 0 { - cmd = libc::F_SETLK; - } - - let ret = unsafe { libc::fcntl(file.as_raw_fd(), cmd, &flock) }; - - if ret < 0 { - Err(Error::last_os_error()) - } else { - Ok(()) - } +fn error_unsupported(err: &std::io::Error) -> bool { + match err.raw_os_error() { + // Unfortunately, depending on the target, these may or may not be the same. + // For targets in which they are the same, the duplicate pattern causes a warning. + #[allow(unreachable_patterns)] + Some(libc::ENOTSUP | libc::EOPNOTSUPP) => true, + Some(libc::ENOSYS) => true, + _ => false, } } #[cfg(windows)] -mod sys { - use std::fs::File; - use std::io::{Error, Result}; - use std::mem; - use std::os::windows::io::AsRawHandle; - - use windows_sys::Win32::Foundation::HANDLE; - use windows_sys::Win32::Foundation::{ERROR_INVALID_FUNCTION, ERROR_LOCK_VIOLATION}; - use windows_sys::Win32::Storage::FileSystem::{ - LOCKFILE_EXCLUSIVE_LOCK, LOCKFILE_FAIL_IMMEDIATELY, LockFileEx, UnlockFile, - }; - - pub(super) fn lock_shared(file: &File) -> Result<()> { - lock_file(file, 0) - } - - pub(super) fn lock_exclusive(file: &File) -> Result<()> { - lock_file(file, LOCKFILE_EXCLUSIVE_LOCK) - } - - pub(super) fn try_lock_shared(file: &File) -> Result<()> { - lock_file(file, LOCKFILE_FAIL_IMMEDIATELY) - } - - pub(super) fn try_lock_exclusive(file: &File) -> Result<()> { - lock_file(file, LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY) - } - - pub(super) fn error_contended(err: &Error) -> bool { - err.raw_os_error() - .map_or(false, |x| x == ERROR_LOCK_VIOLATION as i32) - } - - pub(super) fn error_unsupported(err: &Error) -> bool { - err.raw_os_error() - .map_or(false, |x| x == ERROR_INVALID_FUNCTION as i32) - } - - pub(super) fn unlock(file: &File) -> Result<()> { - unsafe { - let ret = UnlockFile(file.as_raw_handle() as HANDLE, 0, 0, !0, !0); - if ret == 0 { - Err(Error::last_os_error()) - } else { - Ok(()) - } - } - } - - fn lock_file(file: &File, flags: u32) -> Result<()> { - unsafe { - let mut overlapped = mem::zeroed(); - let ret = LockFileEx( - file.as_raw_handle() as HANDLE, - flags, - 0, - !0, - !0, - &mut overlapped, - ); - if ret == 0 { - Err(Error::last_os_error()) - } else { - Ok(()) - } - } - } +fn error_unsupported(err: &std::io::Error) -> bool { + use windows_sys::Win32::Foundation::ERROR_INVALID_FUNCTION; + err.raw_os_error() + .map_or(false, |x| x == ERROR_INVALID_FUNCTION as i32) }