1use crate::errors::{MmapIoError, Result};
4use crate::mmap::MemoryMappedFile;
5use crate::utils::slice_range;
6
7impl MemoryMappedFile {
8 #[cfg(feature = "locking")]
23 pub fn lock(&self, offset: u64, len: u64) -> Result<()> {
24 if len == 0 {
25 return Ok(());
26 }
27
28 let total = self.current_len()?;
29 let (start, end) = slice_range(offset, len, total)?;
30 let length = end - start;
31
32 let ptr = match &self.inner.map {
34 crate::mmap::MapVariant::Ro(m) => m.as_ptr(),
35 crate::mmap::MapVariant::Rw(lock) => {
36 let guard = lock.read();
37 guard.as_ptr()
38 }
39 crate::mmap::MapVariant::Cow(m) => m.as_ptr(),
40 };
41
42 let addr = unsafe { ptr.add(start) };
44
45 #[cfg(unix)]
46 {
47 let result = unsafe {
49 libc::mlock(
50 addr as *const libc::c_void,
51 length,
52 )
53 };
54
55 if result != 0 {
56 let err = std::io::Error::last_os_error();
57 return Err(MmapIoError::LockFailed(format!(
58 "mlock failed: {err}. This operation typically requires elevated privileges."
59 )));
60 }
61 }
62
63 #[cfg(windows)]
64 {
65 use std::ptr;
66
67 extern "system" {
68 fn VirtualLock(
69 lpAddress: *const core::ffi::c_void,
70 dwSize: usize,
71 ) -> i32;
72 }
73
74 let result = unsafe {
76 VirtualLock(
77 addr as *const core::ffi::c_void,
78 length,
79 )
80 };
81
82 if result == 0 {
83 let err = std::io::Error::last_os_error();
84 return Err(MmapIoError::LockFailed(format!(
85 "VirtualLock failed: {err}. This operation may require elevated privileges."
86 )));
87 }
88 }
89
90 Ok(())
91 }
92
93 #[cfg(feature = "locking")]
107 pub fn unlock(&self, offset: u64, len: u64) -> Result<()> {
108 if len == 0 {
109 return Ok(());
110 }
111
112 let total = self.current_len()?;
113 let (start, end) = slice_range(offset, len, total)?;
114 let length = end - start;
115
116 let ptr = match &self.inner.map {
118 crate::mmap::MapVariant::Ro(m) => m.as_ptr(),
119 crate::mmap::MapVariant::Rw(lock) => {
120 let guard = lock.read();
121 guard.as_ptr()
122 }
123 crate::mmap::MapVariant::Cow(m) => m.as_ptr(),
124 };
125
126 let addr = unsafe { ptr.add(start) };
128
129 #[cfg(unix)]
130 {
131 let result = unsafe {
133 libc::munlock(
134 addr as *const libc::c_void,
135 length,
136 )
137 };
138
139 if result != 0 {
140 let err = std::io::Error::last_os_error();
141 return Err(MmapIoError::UnlockFailed(format!(
142 "munlock failed: {err}"
143 )));
144 }
145 }
146
147 #[cfg(windows)]
148 {
149 extern "system" {
150 fn VirtualUnlock(
151 lpAddress: *const core::ffi::c_void,
152 dwSize: usize,
153 ) -> i32;
154 }
155
156 let result = unsafe {
158 VirtualUnlock(
159 addr as *const core::ffi::c_void,
160 length,
161 )
162 };
163
164 if result == 0 {
165 let err = std::io::Error::last_os_error();
166 let err_code = err.raw_os_error().unwrap_or(0);
168 if err_code != 158 { return Err(MmapIoError::UnlockFailed(format!(
170 "VirtualUnlock failed: {err}"
171 )));
172 }
173 }
174 }
175
176 Ok(())
177 }
178
179 #[cfg(feature = "locking")]
187 pub fn lock_all(&self) -> Result<()> {
188 let len = self.current_len()?;
189 self.lock(0, len)
190 }
191
192 #[cfg(feature = "locking")]
200 pub fn unlock_all(&self) -> Result<()> {
201 let len = self.current_len()?;
202 self.unlock(0, len)
203 }
204}
205
206#[cfg(test)]
207mod tests {
208 use super::*;
209 use crate::create_mmap;
210 use std::fs;
211 use std::path::PathBuf;
212
213 fn tmp_path(name: &str) -> PathBuf {
214 let mut p = std::env::temp_dir();
215 p.push(format!("mmap_io_lock_test_{}_{}", name, std::process::id()));
216 p
217 }
218
219 #[test]
220 #[cfg(feature = "locking")]
221 fn test_lock_unlock_operations() {
222 let path = tmp_path("lock_ops");
223 let _ = fs::remove_file(&path);
224
225 let mmap = create_mmap(&path, 8192).expect("create");
226
227 let lock_result = mmap.lock(0, 4096);
232 if lock_result.is_ok() {
233 mmap.unlock(0, 4096).expect("unlock should succeed after lock");
235 } else {
236 println!("Lock failed (expected without privileges): {lock_result:?}");
238 }
239
240 mmap.lock(0, 0).expect("empty lock");
242 mmap.unlock(0, 0).expect("empty unlock");
243
244 assert!(mmap.lock(8192, 1).is_err());
246 assert!(mmap.unlock(8192, 1).is_err());
247
248 let lock_all_result = mmap.lock_all();
250 if lock_all_result.is_ok() {
251 mmap.unlock_all().expect("unlock_all should succeed after lock_all");
252 }
253
254 fs::remove_file(&path).expect("cleanup");
255 }
256
257 #[test]
258 #[cfg(feature = "locking")]
259 fn test_lock_with_different_modes() {
260 let path = tmp_path("lock_modes");
261 let _ = fs::remove_file(&path);
262
263 let mmap = create_mmap(&path, 4096).expect("create");
265 let _ = mmap.lock(0, 1024); drop(mmap);
267
268 let mmap = MemoryMappedFile::open_ro(&path).expect("open ro");
270 let _ = mmap.lock(0, 1024); #[cfg(feature = "cow")]
273 {
274 let mmap = MemoryMappedFile::open_cow(&path).expect("open cow");
276 let _ = mmap.lock(0, 1024); }
278
279 fs::remove_file(&path).expect("cleanup");
280 }
281
282 #[test]
283 #[cfg(all(feature = "locking", unix))]
284 fn test_multiple_lock_regions() {
285 let path = tmp_path("multi_lock");
286 let _ = fs::remove_file(&path);
287
288 let mmap = create_mmap(&path, 16384).expect("create");
289
290 let _ = mmap.lock(0, 4096);
293 let _ = mmap.lock(4096, 4096);
294 let _ = mmap.lock(8192, 4096);
295
296 let _ = mmap.unlock(4096, 4096);
298 let _ = mmap.unlock(0, 4096);
299 let _ = mmap.unlock(8192, 4096);
300
301 fs::remove_file(&path).expect("cleanup");
302 }
303}