microsandbox_utils/
process_lock.rs1use std::fs::{File, OpenOptions};
4use std::io;
5#[cfg(unix)]
6use std::os::fd::AsRawFd;
7#[cfg(unix)]
8use std::os::unix::fs::OpenOptionsExt;
9#[cfg(windows)]
10use std::os::windows::io::AsRawHandle;
11use std::path::Path;
12
13#[cfg(windows)]
14use windows_sys::Win32::Foundation::{ERROR_IO_PENDING, ERROR_LOCK_VIOLATION, HANDLE};
15#[cfg(windows)]
16use windows_sys::Win32::Storage::FileSystem::{
17 LOCKFILE_EXCLUSIVE_LOCK, LOCKFILE_FAIL_IMMEDIATELY, LockFileEx, UnlockFileEx,
18};
19#[cfg(windows)]
20use windows_sys::Win32::System::IO::OVERLAPPED;
21
22pub fn lock_descriptor(file: &File, exclusive: bool, nonblocking: bool) -> io::Result<bool> {
29 #[cfg(unix)]
30 {
31 if exclusive {
32 lock_exclusive_inner(file, nonblocking)
33 } else {
34 lock_shared(file).map(|()| true)
35 }
36 }
37 #[cfg(windows)]
38 {
39 let mut overlapped: OVERLAPPED = unsafe { std::mem::zeroed() };
42 overlapped.Anonymous.Anonymous.Offset = u32::MAX - 1;
43 overlapped.Anonymous.Anonymous.OffsetHigh = u32::MAX;
44 let flags = if exclusive {
45 LOCKFILE_EXCLUSIVE_LOCK
46 } else {
47 0
48 } | if nonblocking {
49 LOCKFILE_FAIL_IMMEDIATELY
50 } else {
51 0
52 };
53 let result = unsafe {
54 LockFileEx(
55 file.as_raw_handle() as HANDLE,
56 flags,
57 0,
58 1,
59 0,
60 &mut overlapped,
61 )
62 };
63 if result != 0 {
64 return Ok(true);
65 }
66 let error = io::Error::last_os_error();
67 if nonblocking
68 && matches!(error.raw_os_error(), Some(code) if code as u32 == ERROR_LOCK_VIOLATION || code as u32 == ERROR_IO_PENDING)
69 {
70 return Ok(false);
71 }
72 Err(error)
73 }
74}
75
76pub fn open_lock_file(path: &Path) -> io::Result<File> {
78 open_lock_file_with(path, true, false)
79}
80
81pub fn open_existing_lock_file(path: &Path) -> io::Result<File> {
83 open_lock_file_with(path, false, false)
84}
85
86pub fn create_new_lock_file(path: &Path) -> io::Result<File> {
88 open_lock_file_with(path, false, true)
89}
90
91pub fn lock_exclusive(file: &File) -> io::Result<()> {
93 lock_exclusive_inner(file, false).map(|_| ())
94}
95
96pub fn lock_shared(file: &File) -> io::Result<()> {
98 #[cfg(unix)]
99 loop {
100 if unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_SH) } == 0 {
101 return Ok(());
102 }
103 let error = io::Error::last_os_error();
104 if error.kind() != io::ErrorKind::Interrupted {
105 return Err(error);
106 }
107 }
108 #[cfg(windows)]
109 {
110 let mut overlapped: OVERLAPPED = unsafe { std::mem::zeroed() };
111 let result = unsafe {
112 LockFileEx(
113 file.as_raw_handle() as HANDLE,
114 0,
115 0,
116 u32::MAX,
117 u32::MAX,
118 &mut overlapped,
119 )
120 };
121 if result == 0 {
122 return Err(io::Error::last_os_error());
123 }
124 Ok(())
125 }
126}
127
128pub fn try_lock_exclusive(file: &File) -> io::Result<bool> {
132 lock_exclusive_inner(file, true)
133}
134
135pub fn unlock(file: &File) -> io::Result<()> {
137 #[cfg(unix)]
138 {
139 let result = unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_UN) };
140 if result != 0 {
141 return Err(io::Error::last_os_error());
142 }
143 }
144
145 #[cfg(windows)]
146 {
147 let mut overlapped: OVERLAPPED = unsafe { std::mem::zeroed() };
148 let result = unsafe {
149 UnlockFileEx(
150 file.as_raw_handle() as HANDLE,
151 0,
152 u32::MAX,
153 u32::MAX,
154 &mut overlapped,
155 )
156 };
157 if result == 0 {
158 return Err(io::Error::last_os_error());
159 }
160 }
161
162 Ok(())
163}
164
165#[cfg(unix)]
166fn lock_exclusive_inner(file: &File, nonblocking: bool) -> io::Result<bool> {
167 let operation = if nonblocking {
168 libc::LOCK_EX | libc::LOCK_NB
169 } else {
170 libc::LOCK_EX
171 };
172 let result = unsafe { libc::flock(file.as_raw_fd(), operation) };
173 if result == 0 {
174 return Ok(true);
175 }
176
177 let error = io::Error::last_os_error();
178 if nonblocking
179 && matches!(
180 error.raw_os_error(),
181 Some(code) if code == libc::EWOULDBLOCK || code == libc::EAGAIN
182 )
183 {
184 return Ok(false);
185 }
186 Err(error)
187}
188
189fn open_lock_file_with(path: &Path, create: bool, create_new: bool) -> io::Result<File> {
190 let mut options = OpenOptions::new();
191 options
192 .create(create)
193 .create_new(create_new)
194 .truncate(false)
195 .read(true)
196 .write(true);
197 #[cfg(unix)]
198 options.mode(0o600).custom_flags(libc::O_NOFOLLOW);
199 options.open(path)
200}
201
202#[cfg(windows)]
203fn lock_exclusive_inner(file: &File, nonblocking: bool) -> io::Result<bool> {
204 let mut overlapped: OVERLAPPED = unsafe { std::mem::zeroed() };
205 let flags = LOCKFILE_EXCLUSIVE_LOCK
206 | if nonblocking {
207 LOCKFILE_FAIL_IMMEDIATELY
208 } else {
209 0
210 };
211 let result = unsafe {
212 LockFileEx(
213 file.as_raw_handle() as HANDLE,
214 flags,
215 0,
216 u32::MAX,
217 u32::MAX,
218 &mut overlapped,
219 )
220 };
221 if result != 0 {
222 return Ok(true);
223 }
224
225 let error = io::Error::last_os_error();
226 if nonblocking
227 && matches!(
228 error.raw_os_error(),
229 Some(code) if code as u32 == ERROR_LOCK_VIOLATION || code as u32 == ERROR_IO_PENDING
230 )
231 {
232 return Ok(false);
233 }
234 Err(error)
235}
236
237#[cfg(test)]
242mod tests {
243 use super::*;
244
245 #[test]
246 fn process_lock_is_exclusive_and_reusable() {
247 let dir = tempfile::tempdir().unwrap();
248 let path = dir.path().join("lease.lock");
249 let first = open_lock_file(&path).unwrap();
250 let second = open_lock_file(&path).unwrap();
251
252 assert!(try_lock_exclusive(&first).unwrap());
253 assert!(!try_lock_exclusive(&second).unwrap());
254 unlock(&first).unwrap();
255 assert!(try_lock_exclusive(&second).unwrap());
256 }
257}