@@ -66,7 +66,7 @@ pub fn path_exists<P: AsRef<Path>>(path: P) -> bool {
6666
6767pub ( 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) ]
393495mod tests {
394496 use super :: * ;
0 commit comments