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 try_lock_exclusive(file: &File) -> io::Result<bool> {
50 lock_exclusive_inner(file, true)
51}
52
53pub fn unlock(file: &File) -> io::Result<()> {
55 #[cfg(unix)]
56 {
57 let result = unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_UN) };
58 if result != 0 {
59 return Err(io::Error::last_os_error());
60 }
61 }
62
63 #[cfg(windows)]
64 {
65 let mut overlapped: OVERLAPPED = unsafe { std::mem::zeroed() };
66 let result = unsafe {
67 UnlockFileEx(
68 file.as_raw_handle() as HANDLE,
69 0,
70 u32::MAX,
71 u32::MAX,
72 &mut overlapped,
73 )
74 };
75 if result == 0 {
76 return Err(io::Error::last_os_error());
77 }
78 }
79
80 Ok(())
81}
82
83#[cfg(unix)]
84fn lock_exclusive_inner(file: &File, nonblocking: bool) -> io::Result<bool> {
85 let operation = if nonblocking {
86 libc::LOCK_EX | libc::LOCK_NB
87 } else {
88 libc::LOCK_EX
89 };
90 let result = unsafe { libc::flock(file.as_raw_fd(), operation) };
91 if result == 0 {
92 return Ok(true);
93 }
94
95 let error = io::Error::last_os_error();
96 if nonblocking
97 && matches!(
98 error.raw_os_error(),
99 Some(code) if code == libc::EWOULDBLOCK || code == libc::EAGAIN
100 )
101 {
102 return Ok(false);
103 }
104 Err(error)
105}
106
107fn open_lock_file_with(path: &Path, create: bool, create_new: bool) -> io::Result<File> {
108 let mut options = OpenOptions::new();
109 options
110 .create(create)
111 .create_new(create_new)
112 .truncate(false)
113 .read(true)
114 .write(true);
115 #[cfg(unix)]
116 options.mode(0o600).custom_flags(libc::O_NOFOLLOW);
117 options.open(path)
118}
119
120#[cfg(windows)]
121fn lock_exclusive_inner(file: &File, nonblocking: bool) -> io::Result<bool> {
122 let mut overlapped: OVERLAPPED = unsafe { std::mem::zeroed() };
123 let flags = LOCKFILE_EXCLUSIVE_LOCK
124 | if nonblocking {
125 LOCKFILE_FAIL_IMMEDIATELY
126 } else {
127 0
128 };
129 let result = unsafe {
130 LockFileEx(
131 file.as_raw_handle() as HANDLE,
132 flags,
133 0,
134 u32::MAX,
135 u32::MAX,
136 &mut overlapped,
137 )
138 };
139 if result != 0 {
140 return Ok(true);
141 }
142
143 let error = io::Error::last_os_error();
144 if nonblocking
145 && matches!(
146 error.raw_os_error(),
147 Some(code) if code as u32 == ERROR_LOCK_VIOLATION || code as u32 == ERROR_IO_PENDING
148 )
149 {
150 return Ok(false);
151 }
152 Err(error)
153}
154
155#[cfg(test)]
160mod tests {
161 use super::*;
162
163 #[test]
164 fn process_lock_is_exclusive_and_reusable() {
165 let dir = tempfile::tempdir().unwrap();
166 let path = dir.path().join("lease.lock");
167 let first = open_lock_file(&path).unwrap();
168 let second = open_lock_file(&path).unwrap();
169
170 assert!(try_lock_exclusive(&first).unwrap());
171 assert!(!try_lock_exclusive(&second).unwrap());
172 unlock(&first).unwrap();
173 assert!(try_lock_exclusive(&second).unwrap());
174 }
175}