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 lock_shared_inner(file, false).map(|_| ())
99}
100
101pub fn try_lock_shared(file: &File) -> io::Result<bool> {
105 lock_shared_inner(file, true)
106}
107
108#[cfg(unix)]
109fn lock_shared_inner(file: &File, nonblocking: bool) -> io::Result<bool> {
110 let operation = if nonblocking {
111 libc::LOCK_SH | libc::LOCK_NB
112 } else {
113 libc::LOCK_SH
114 };
115 loop {
116 if unsafe { libc::flock(file.as_raw_fd(), operation) } == 0 {
117 return Ok(true);
118 }
119 let error = io::Error::last_os_error();
120 if error.kind() == io::ErrorKind::Interrupted {
121 continue;
122 }
123 if nonblocking
124 && matches!(
125 error.raw_os_error(),
126 Some(code) if code == libc::EWOULDBLOCK || code == libc::EAGAIN
127 )
128 {
129 return Ok(false);
130 }
131 return Err(error);
132 }
133}
134
135#[cfg(windows)]
136fn lock_shared_inner(file: &File, nonblocking: bool) -> io::Result<bool> {
137 let mut overlapped: OVERLAPPED = unsafe { std::mem::zeroed() };
138 let flags = if nonblocking {
139 LOCKFILE_FAIL_IMMEDIATELY
140 } else {
141 0
142 };
143 let result = unsafe {
144 LockFileEx(
145 file.as_raw_handle() as HANDLE,
146 flags,
147 0,
148 u32::MAX,
149 u32::MAX,
150 &mut overlapped,
151 )
152 };
153 if result != 0 {
154 return Ok(true);
155 }
156
157 let error = io::Error::last_os_error();
158 if nonblocking
159 && matches!(
160 error.raw_os_error(),
161 Some(code) if code as u32 == ERROR_LOCK_VIOLATION || code as u32 == ERROR_IO_PENDING
162 )
163 {
164 return Ok(false);
165 }
166 Err(error)
167}
168
169pub fn try_lock_exclusive(file: &File) -> io::Result<bool> {
173 lock_exclusive_inner(file, true)
174}
175
176pub fn unlock(file: &File) -> io::Result<()> {
178 #[cfg(unix)]
179 {
180 let result = unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_UN) };
181 if result != 0 {
182 return Err(io::Error::last_os_error());
183 }
184 }
185
186 #[cfg(windows)]
187 {
188 let mut overlapped: OVERLAPPED = unsafe { std::mem::zeroed() };
189 let result = unsafe {
190 UnlockFileEx(
191 file.as_raw_handle() as HANDLE,
192 0,
193 u32::MAX,
194 u32::MAX,
195 &mut overlapped,
196 )
197 };
198 if result == 0 {
199 return Err(io::Error::last_os_error());
200 }
201 }
202
203 Ok(())
204}
205
206#[cfg(unix)]
207fn lock_exclusive_inner(file: &File, nonblocking: bool) -> io::Result<bool> {
208 let operation = if nonblocking {
209 libc::LOCK_EX | libc::LOCK_NB
210 } else {
211 libc::LOCK_EX
212 };
213 let result = unsafe { libc::flock(file.as_raw_fd(), operation) };
214 if result == 0 {
215 return Ok(true);
216 }
217
218 let error = io::Error::last_os_error();
219 if nonblocking
220 && matches!(
221 error.raw_os_error(),
222 Some(code) if code == libc::EWOULDBLOCK || code == libc::EAGAIN
223 )
224 {
225 return Ok(false);
226 }
227 Err(error)
228}
229
230fn open_lock_file_with(path: &Path, create: bool, create_new: bool) -> io::Result<File> {
231 let mut options = OpenOptions::new();
232 options
233 .create(create)
234 .create_new(create_new)
235 .truncate(false)
236 .read(true)
237 .write(true);
238 #[cfg(unix)]
239 options.mode(0o600).custom_flags(libc::O_NOFOLLOW);
240 options.open(path)
241}
242
243#[cfg(windows)]
244fn lock_exclusive_inner(file: &File, nonblocking: bool) -> io::Result<bool> {
245 let mut overlapped: OVERLAPPED = unsafe { std::mem::zeroed() };
246 let flags = LOCKFILE_EXCLUSIVE_LOCK
247 | if nonblocking {
248 LOCKFILE_FAIL_IMMEDIATELY
249 } else {
250 0
251 };
252 let result = unsafe {
253 LockFileEx(
254 file.as_raw_handle() as HANDLE,
255 flags,
256 0,
257 u32::MAX,
258 u32::MAX,
259 &mut overlapped,
260 )
261 };
262 if result != 0 {
263 return Ok(true);
264 }
265
266 let error = io::Error::last_os_error();
267 if nonblocking
268 && matches!(
269 error.raw_os_error(),
270 Some(code) if code as u32 == ERROR_LOCK_VIOLATION || code as u32 == ERROR_IO_PENDING
271 )
272 {
273 return Ok(false);
274 }
275 Err(error)
276}
277
278#[cfg(test)]
283mod tests {
284 use super::*;
285
286 #[test]
287 fn process_lock_is_exclusive_and_reusable() {
288 let dir = tempfile::tempdir().unwrap();
289 let path = dir.path().join("lease.lock");
290 let first = open_lock_file(&path).unwrap();
291 let second = open_lock_file(&path).unwrap();
292
293 assert!(try_lock_exclusive(&first).unwrap());
294 assert!(!try_lock_exclusive(&second).unwrap());
295 unlock(&first).unwrap();
296 assert!(try_lock_exclusive(&second).unwrap());
297 }
298
299 #[test]
300 fn shared_locks_are_compatible_and_exclude_writers() {
301 let dir = tempfile::tempdir().unwrap();
302 let path = dir.path().join("lease.lock");
303 let first_reader = open_lock_file(&path).unwrap();
304 let second_reader = open_lock_file(&path).unwrap();
305 let writer = open_lock_file(&path).unwrap();
306
307 assert!(try_lock_shared(&first_reader).unwrap());
308 assert!(try_lock_shared(&second_reader).unwrap());
309 assert!(!try_lock_exclusive(&writer).unwrap());
310 unlock(&first_reader).unwrap();
311 assert!(!try_lock_exclusive(&writer).unwrap());
312 unlock(&second_reader).unwrap();
313 assert!(try_lock_exclusive(&writer).unwrap());
314 }
315}