From 4a2ec556f9bf081784145f64815beb1f99c880c9 Mon Sep 17 00:00:00 2001 From: Petr Sumbera Date: Wed, 19 Aug 2026 06:50:40 -0700 Subject: [PATCH] [PATCH] bootstrap: use fcntl locking on Solaris Solaris does not provide flock, so std::fs::File deliberately does not support its locking API there. Its earlier fcntl emulation was removed because fcntl locks are process-scoped rather than handle-scoped. The bootstrap build lock only coordinates separate bootstrap processes and is held for the lifetime of the process, making fcntl locking suitable for this narrower use. Add a Solaris-specific backend while retaining std file locking on other platforms. --- src/bootstrap/src/cli_main.rs | 66 +++++++++++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 2 deletions(-) diff --git a/src/bootstrap/src/cli_main.rs b/src/bootstrap/src/cli_main.rs index c11e1478f4d42..39e7d77796d0f 100644 --- a/src/bootstrap/src/cli_main.rs +++ b/src/bootstrap/src/cli_main.rs @@ -81,7 +81,7 @@ pub fn main() { .create(true) .truncate(false) .open(&lock_path)); - t!(build_lock.try_lock().or_else(|e| { + t!(build_lock::try_lock(&build_lock).or_else(|e| { if let TryLockError::Error(e) = e { return Err(e); } @@ -94,7 +94,7 @@ pub fn main() { } else { println!("WARNING: build directory locked, waiting for lock"); } - build_lock.lock() + build_lock::lock(&build_lock) })); t!(build_lock.set_len(0)); t!(build_lock.write_all(process::id().to_string().as_bytes())); @@ -219,6 +219,68 @@ pub fn main() { } } +#[cfg(not(target_os = "solaris"))] +mod build_lock { + use std::fs::{File, TryLockError}; + use std::io; + + pub fn try_lock(file: &File) -> Result<(), TryLockError> { + file.try_lock() + } + + pub fn lock(file: &File) -> io::Result<()> { + file.lock() + } +} + +// Solaris does not provide `flock`, and `std::fs::File` deliberately does not +// emulate its handle-scoped locking API with process-scoped `fcntl` locks. The +// bootstrap lock, however, coordinates separate bootstrap processes and is +// held for the lifetime of the process, so `fcntl` has the semantics it needs. +#[cfg(target_os = "solaris")] +mod build_lock { + use std::fs::{File, TryLockError}; + use std::os::fd::AsRawFd; + use std::{io, mem}; + + pub fn try_lock(file: &File) -> Result<(), TryLockError> { + match fcntl_lock(file, libc::F_SETLK) { + Ok(()) => Ok(()), + Err(error) if is_would_block(&error) => Err(TryLockError::WouldBlock), + Err(error) => Err(TryLockError::Error(error)), + } + } + + pub fn lock(file: &File) -> io::Result<()> { + fcntl_lock(file, libc::F_SETLKW) + } + + fn fcntl_lock(file: &File, command: libc::c_int) -> io::Result<()> { + let mut lock = unsafe { mem::zeroed::() }; + lock.l_type = libc::F_WRLCK as libc::c_short; + lock.l_whence = libc::SEEK_SET as libc::c_short; + lock.l_start = 0; + lock.l_len = 0; + + loop { + if unsafe { libc::fcntl(file.as_raw_fd(), command, &mut lock) } != -1 { + return Ok(()); + } + + let error = io::Error::last_os_error(); + if command == libc::F_SETLKW && error.kind() == io::ErrorKind::Interrupted { + continue; + } + return Err(error); + } + } + + fn is_would_block(error: &io::Error) -> bool { + matches!(error.raw_os_error(), Some(libc::EACCES | libc::EAGAIN)) + || error.kind() == io::ErrorKind::WouldBlock + } +} + fn check_version(config: &Config) -> Option { let mut msg = String::new();