Skip to content

Commit 04a22b5

Browse files
ChrisDentonrami3l
authored andcommitted
Support file locking on Solaris
1 parent 9e818e8 commit 04a22b5

3 files changed

Lines changed: 130 additions & 6 deletions

File tree

‎clippy.toml‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
[[disallowed-methods]]
2+
path = "std::fs::File::lock"
3+
reason = "does not support Solaris"
4+
replacement = "crate::utils::raw::lock"
5+
6+
[[disallowed-methods]]
7+
path = "std::fs::File::lock_shared"
8+
reason = "does not support Solaris"
9+
replacement = "crate::utils::raw::lock_shared"
10+
11+
[[disallowed-methods]]
12+
path = "std::fs::File::try_lock"
13+
reason = "does not support Solaris"
14+
replacement = "crate::utils::raw::try_lock"
15+
16+
[[disallowed-methods]]
17+
path = "std::fs::File::try_lock_shared"
18+
reason = "needs a Solaris implementation in crate::utils::raw"
19+
20+
[[disallowed-methods]]
21+
path = "std::fs::File::unlock"
22+
reason = "needs a Solaris implementation in crate::utils::raw"

‎src/cli/self_update/stage.rs‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,13 @@ pub(super) struct SelfUpdateLock {
7070
impl SelfUpdateLock {
7171
pub(super) fn lock(process: &Process) -> anyhow::Result<Self> {
7272
let lock = Self::open(process)?;
73-
lock._file.lock().context("failed to lock self-update")?;
73+
utils::raw::lock(&lock._file).context("failed to lock self-update")?;
7474
Ok(lock)
7575
}
7676

7777
fn try_lock(process: &Process) -> anyhow::Result<Option<Self>> {
7878
let lock = Self::open(process)?;
79-
match lock._file.try_lock() {
79+
match utils::raw::try_lock(&lock._file) {
8080
Ok(()) => Ok(Some(lock)),
8181
Err(fs::TryLockError::WouldBlock) => Ok(None),
8282
Err(fs::TryLockError::Error(error)) => Err(error).context("failed to lock self-update"),
@@ -331,11 +331,11 @@ mod tests {
331331
.unwrap();
332332

333333
assert!(matches!(
334-
contender.try_lock(),
334+
utils::raw::try_lock(&contender),
335335
Err(fs::TryLockError::WouldBlock)
336336
));
337337
drop(lock);
338-
contender.try_lock().unwrap();
338+
utils::raw::try_lock(&contender).unwrap();
339339
}
340340

341341
#[tokio::test]

‎src/utils/raw.rs‎

Lines changed: 104 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ pub fn path_exists<P: AsRef<Path>>(path: P) -> bool {
6666

6767
pub(crate) fn read_locked_file(path: &Path) -> io::Result<String> {
6868
let mut file = File::open(path)?;
69-
file.lock_shared()?;
69+
lock_shared(&file)?;
7070

7171
let size = file
7272
.metadata()
@@ -110,7 +110,7 @@ pub(crate) fn write_locked_file(path: &Path, contents: &str) -> io::Result<()> {
110110
.truncate(false)
111111
.open(path)?;
112112

113-
file.lock()?;
113+
lock(&file)?;
114114
file.set_len(0)?;
115115
file.seek(SeekFrom::Start(0))?;
116116
Write::write_all(&mut file, contents.as_bytes())?;
@@ -389,6 +389,108 @@ pub(crate) mod windows {
389389
}
390390
}
391391

392+
/// File-locking support.
393+
///
394+
/// On most platforms this is a thin wrapper around `File::lock` and `File::lock_shared`.
395+
/// However, this isn't supported on Solaris[^1] so we instead implement it ourselves
396+
/// using `fcntl`.
397+
///
398+
/// [^1]: See [rust-lang/rust#157390](https://github.com/rust-lang/rust/issues/157390) for more
399+
pub use flock::{lock, lock_shared, try_lock};
400+
401+
#[cfg(not(target_os = "solaris"))]
402+
mod flock {
403+
use std::{
404+
fs::{File, TryLockError},
405+
io,
406+
};
407+
408+
pub fn lock(file: &File) -> io::Result<()> {
409+
#[expect(clippy::disallowed_methods)]
410+
file.lock()
411+
}
412+
413+
pub fn lock_shared(file: &File) -> io::Result<()> {
414+
#[expect(clippy::disallowed_methods)]
415+
file.lock_shared()
416+
}
417+
418+
pub fn try_lock(file: &File) -> Result<(), TryLockError> {
419+
#[expect(clippy::disallowed_methods)]
420+
file.try_lock()
421+
}
422+
}
423+
424+
#[cfg(target_os = "solaris")]
425+
mod flock {
426+
// Code adapted from Cargo:
427+
// https://github.com/rust-lang/cargo/blob/b57863321f8d0a0f76fcf9ad636b6e39b024d0c0/src/util/flock.rs
428+
use std::{
429+
fs::{File, TryLockError},
430+
io, mem,
431+
os::unix::io::AsRawFd,
432+
};
433+
434+
pub fn lock(file: &File) -> io::Result<()> {
435+
fcntl_lock(file, libc::F_WRLCK, FcntlCmd::F_SETLKW)
436+
}
437+
438+
pub fn lock_shared(file: &File) -> io::Result<()> {
439+
fcntl_lock(file, libc::F_RDLCK, FcntlCmd::F_SETLKW)
440+
}
441+
442+
pub fn try_lock(file: &File) -> Result<(), TryLockError> {
443+
match fcntl_lock(file, libc::F_WRLCK, FcntlCmd::F_SETLK) {
444+
Ok(()) => Ok(()),
445+
Err(e) if is_would_block(&e) => Err(TryLockError::WouldBlock),
446+
Err(e) => Err(TryLockError::Error(e)),
447+
}
448+
}
449+
450+
fn fcntl_lock(file: &File, lock_type: libc::c_short, cmd: FcntlCmd) -> io::Result<()> {
451+
let mut lock = flock_for_whole_file(lock_type);
452+
loop {
453+
// SAFETY:
454+
// fcntl takes a variable number of additonal arguments depending on `cmd`.
455+
// In our case `cmd` is either F_SETLK or F_SETLKW which takes a single
456+
// argument, which is a pointer to a mutable `flock`.
457+
// We have exclusive ownership of `lock` so it's safe to pass.
458+
let result = unsafe { libc::fcntl(file.as_raw_fd(), cmd as libc::c_int, &mut lock) };
459+
if result != -1 {
460+
return Ok(());
461+
}
462+
let error = io::Error::last_os_error();
463+
if cmd == FcntlCmd::F_SETLKW && error.kind() == io::ErrorKind::Interrupted {
464+
continue;
465+
}
466+
return Err(error);
467+
}
468+
}
469+
470+
#[repr(i32)]
471+
#[derive(Copy, Clone, Eq, PartialEq)]
472+
#[expect(nonstandard_style)]
473+
enum FcntlCmd {
474+
F_SETLK = libc::F_SETLK,
475+
F_SETLKW = libc::F_SETLKW,
476+
}
477+
478+
fn flock_for_whole_file(lock_type: libc::c_short) -> libc::flock {
479+
// SAFETY: libc::flock is a plain C data structure, so can be zeroed.
480+
let mut lock = unsafe { mem::zeroed::<libc::flock>() };
481+
lock.l_type = lock_type;
482+
lock.l_whence = libc::SEEK_SET as libc::c_short;
483+
lock.l_start = 0;
484+
lock.l_len = 0;
485+
lock
486+
}
487+
488+
fn is_would_block(error: &io::Error) -> bool {
489+
matches!(error.raw_os_error(), Some(libc::EACCES | libc::EAGAIN))
490+
|| error.kind() == io::ErrorKind::WouldBlock
491+
}
492+
}
493+
392494
#[cfg(test)]
393495
mod tests {
394496
use super::*;

0 commit comments

Comments
 (0)