Skip to main content

rustc_data_structures/flock/
unix.rs

1use std::collections::hash_map::Entry;
2use std::fs::File;
3use std::os::unix::prelude::*;
4use std::path::{Path, PathBuf};
5use std::sync::{Arc, LazyLock, Mutex};
6use std::{io, mem};
7
8use rustc_hash::FxHashMap;
9
10static LOCK_REGISTRY: LazyLock<Mutex<FxHashMap<PathBuf, LockState>>> =
11    LazyLock::new(|| Mutex::new(FxHashMap::default()));
12
13enum LockState {
14    /// Lock exclusively held. `Lock.file` contains an `Arc<UnlockGuard>` with a single reference.
15    ///
16    /// The `extra_files` fields contains files we opened while the lock was held. We have to
17    /// persist them until we actually want to unlock the file to prevent unlocking on close.
18    Exclusive { extra_files: Vec<File> },
19    /// Lock can be shared. When there are N lock holders, the `Arc<UnlockGuard>` has N+1 references
20    /// with the last one being held by `LockState` and getting removed in the drop impl of `Lock`
21    /// if it is the remaining reference.
22    Shared(Arc<UnlockGuard>),
23}
24
25#[derive(#[automatically_derived]
impl ::core::fmt::Debug for Lock {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field2_finish(f, "Lock", "path",
            &self.path, "file", &&self.file)
    }
}Debug)]
26pub struct Lock {
27    path: PathBuf,
28    file: Option<Arc<UnlockGuard>>,
29}
30
31impl Lock {
32    pub fn try_lock(p: &Path, file: File, exclusive: bool) -> io::Result<Lock> {
33        let mut locks = LOCK_REGISTRY.lock().unwrap();
34
35        let file = match locks.entry(p.to_owned()) {
36            Entry::Occupied(mut state) => {
37                // We must not open the file again if there is an existing lock to prevent the close
38                // from unlocking the file even when another `Lock` already had the lock held before
39                // this `Lock::try_lock` call.
40                match state.get_mut() {
41                    LockState::Exclusive { extra_files } => {
42                        // Retain file to prevent unlock on close
43                        extra_files.push(file);
44
45                        return Err(io::ErrorKind::WouldBlock.into());
46                    }
47                    LockState::Shared(file) => {
48                        if exclusive {
49                            return Err(io::ErrorKind::WouldBlock.into());
50                        } else {
51                            Arc::clone(file)
52                        }
53                    }
54                }
55            }
56            Entry::Vacant(vacant) => {
57                let file = Arc::new(UnlockGuard::try_lock(file, exclusive)?);
58
59                if exclusive {
60                    vacant.insert(LockState::Exclusive { extra_files: ::alloc::vec::Vec::new()vec![] });
61                } else {
62                    vacant.insert(LockState::Shared(Arc::clone(&file)));
63                }
64
65                file
66            }
67        };
68
69        Ok(Lock { path: p.to_owned(), file: Some(file) })
70    }
71}
72
73impl Drop for Lock {
74    fn drop(&mut self) {
75        let mut locks = LOCK_REGISTRY.lock().unwrap();
76        self.file.take().unwrap();
77        match locks.get_mut(&self.path).unwrap() {
78            LockState::Exclusive { extra_files: _ } => {
79                locks.remove(&self.path);
80            }
81            LockState::Shared(file) => {
82                if Arc::strong_count(file) == 1 {
83                    locks.remove(&self.path);
84                }
85            }
86        }
87    }
88}
89
90/// A file guard which will unlock the file when dropped.
91#[derive(#[automatically_derived]
impl ::core::fmt::Debug for UnlockGuard {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field1_finish(f, "UnlockGuard",
            "file", &&self.file)
    }
}Debug)]
92struct UnlockGuard {
93    file: File,
94}
95
96impl UnlockGuard {
97    fn try_lock(file: File, exclusive: bool) -> io::Result<Self> {
98        let lock_type = if exclusive { libc::F_WRLCK } else { libc::F_RDLCK };
99
100        let mut flock: libc::flock = unsafe { mem::zeroed() };
101        #[cfg(not(all(target_os = "hurd", target_arch = "x86")))]
102        {
103            flock.l_type = lock_type as libc::c_short;
104            flock.l_whence = libc::SEEK_SET as libc::c_short;
105        }
106        #[cfg(all(target_os = "hurd", target_arch = "x86"))]
107        {
108            flock.l_type = lock_type as libc::c_int;
109            flock.l_whence = libc::SEEK_SET as libc::c_int;
110        }
111        flock.l_start = 0;
112        flock.l_len = 0;
113
114        let ret = unsafe { libc::fcntl(file.as_raw_fd(), libc::F_SETLK, &flock) };
115        if ret == -1 { Err(io::Error::last_os_error()) } else { Ok(Self { file }) }
116    }
117}
118
119impl Drop for UnlockGuard {
120    fn drop(&mut self) {
121        let mut flock: libc::flock = unsafe { mem::zeroed() };
122        #[cfg(not(all(target_os = "hurd", target_arch = "x86")))]
123        {
124            flock.l_type = libc::F_UNLCK as libc::c_short;
125            flock.l_whence = libc::SEEK_SET as libc::c_short;
126        }
127        #[cfg(all(target_os = "hurd", target_arch = "x86"))]
128        {
129            flock.l_type = libc::F_UNLCK as libc::c_int;
130            flock.l_whence = libc::SEEK_SET as libc::c_int;
131        }
132        flock.l_start = 0;
133        flock.l_len = 0;
134
135        unsafe {
136            libc::fcntl(self.file.as_raw_fd(), libc::F_SETLK, &flock);
137        }
138    }
139}