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 open_lock_file(path: &Path) -> io::Result<File> {
28 open_lock_file_with(path, true, false)
29}
30
31pub fn open_existing_lock_file(path: &Path) -> io::Result<File> {
33 open_lock_file_with(path, false, false)
34}
35
36pub fn create_new_lock_file(path: &Path) -> io::Result<File> {
38 open_lock_file_with(path, false, true)
39}
40
41pub fn lock_exclusive(file: &File) -> io::Result<()> {
43 lock_exclusive_inner(file, false).map(|_| ())
44}
45
46pub fn lock_shared(file: &File) -> io::Result<()> {
48 #[cfg(unix)]
49 loop {
50 if unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_SH) } == 0 {
51 return Ok(());
52 }
53 let error = io::Error::last_os_error();
54 if error.kind() != io::ErrorKind::Interrupted {
55 return Err(error);
56 }
57 }
58 #[cfg(windows)]
59 {
60 let mut overlapped: OVERLAPPED = unsafe { std::mem::zeroed() };
61 let result = unsafe {
62 LockFileEx(
63 file.as_raw_handle() as HANDLE,
64 0,
65 0,
66 u32::MAX,
67 u32::MAX,
68 &mut overlapped,
69 )
70 };
71 if result == 0 {
72 return Err(io::Error::last_os_error());
73 }
74 Ok(())
75 }
76}
77
78pub fn try_lock_exclusive(file: &File) -> io::Result<bool> {
82 lock_exclusive_inner(file, true)
83}
84
85pub fn unlock(file: &File) -> io::Result<()> {
87 #[cfg(unix)]
88 {
89 let result = unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_UN) };
90 if result != 0 {
91 return Err(io::Error::last_os_error());
92 }
93 }
94
95 #[cfg(windows)]
96 {
97 let mut overlapped: OVERLAPPED = unsafe { std::mem::zeroed() };
98 let result = unsafe {
99 UnlockFileEx(
100 file.as_raw_handle() as HANDLE,
101 0,
102 u32::MAX,
103 u32::MAX,
104 &mut overlapped,
105 )
106 };
107 if result == 0 {
108 return Err(io::Error::last_os_error());
109 }
110 }
111
112 Ok(())
113}
114
115#[cfg(unix)]
116fn lock_exclusive_inner(file: &File, nonblocking: bool) -> io::Result<bool> {
117 let operation = if nonblocking {
118 libc::LOCK_EX | libc::LOCK_NB
119 } else {
120 libc::LOCK_EX
121 };
122 let result = unsafe { libc::flock(file.as_raw_fd(), operation) };
123 if result == 0 {
124 return Ok(true);
125 }
126
127 let error = io::Error::last_os_error();
128 if nonblocking
129 && matches!(
130 error.raw_os_error(),
131 Some(code) if code == libc::EWOULDBLOCK || code == libc::EAGAIN
132 )
133 {
134 return Ok(false);
135 }
136 Err(error)
137}
138
139fn open_lock_file_with(path: &Path, create: bool, create_new: bool) -> io::Result<File> {
140 let mut options = OpenOptions::new();
141 options
142 .create(create)
143 .create_new(create_new)
144 .truncate(false)
145 .read(true)
146 .write(true);
147 #[cfg(unix)]
148 options.mode(0o600).custom_flags(libc::O_NOFOLLOW);
149 options.open(path)
150}
151
152#[cfg(windows)]
153fn lock_exclusive_inner(file: &File, nonblocking: bool) -> io::Result<bool> {
154 let mut overlapped: OVERLAPPED = unsafe { std::mem::zeroed() };
155 let flags = LOCKFILE_EXCLUSIVE_LOCK
156 | if nonblocking {
157 LOCKFILE_FAIL_IMMEDIATELY
158 } else {
159 0
160 };
161 let result = unsafe {
162 LockFileEx(
163 file.as_raw_handle() as HANDLE,
164 flags,
165 0,
166 u32::MAX,
167 u32::MAX,
168 &mut overlapped,
169 )
170 };
171 if result != 0 {
172 return Ok(true);
173 }
174
175 let error = io::Error::last_os_error();
176 if nonblocking
177 && matches!(
178 error.raw_os_error(),
179 Some(code) if code as u32 == ERROR_LOCK_VIOLATION || code as u32 == ERROR_IO_PENDING
180 )
181 {
182 return Ok(false);
183 }
184 Err(error)
185}
186
187#[cfg(test)]
192mod tests {
193 use super::*;
194
195 #[test]
196 fn process_lock_is_exclusive_and_reusable() {
197 let dir = tempfile::tempdir().unwrap();
198 let path = dir.path().join("lease.lock");
199 let first = open_lock_file(&path).unwrap();
200 let second = open_lock_file(&path).unwrap();
201
202 assert!(try_lock_exclusive(&first).unwrap());
203 assert!(!try_lock_exclusive(&second).unwrap());
204 unlock(&first).unwrap();
205 assert!(try_lock_exclusive(&second).unwrap());
206 }
207}