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 { libc::mlock(addr as *const libc::c_void, length) };
49
50 if result != 0 {
51 let err = std::io::Error::last_os_error();
52 return Err(MmapIoError::LockFailed(format!(
53 "mlock failed: {err}. This operation typically requires elevated privileges."
54 )));
55 }
56 }
57
58 #[cfg(windows)]
59 {
60 use std::ptr;
61
62 extern "system" {
63 fn VirtualLock(lpAddress: *const core::ffi::c_void, dwSize: usize) -> i32;
64 }
65
66 let result = unsafe { VirtualLock(addr as *const core::ffi::c_void, length) };
68
69 if result == 0 {
70 let err = std::io::Error::last_os_error();
71 return Err(MmapIoError::LockFailed(format!(
72 "VirtualLock failed: {err}. This operation may require elevated privileges."
73 )));
74 }
75 }
76
77 Ok(())
78 }
79
80 #[cfg(feature = "locking")]
94 pub fn unlock(&self, offset: u64, len: u64) -> Result<()> {
95 if len == 0 {
96 return Ok(());
97 }
98
99 let total = self.current_len()?;
100 let (start, end) = slice_range(offset, len, total)?;
101 let length = end - start;
102
103 let ptr = match &self.inner.map {
105 crate::mmap::MapVariant::Ro(m) => m.as_ptr(),
106 crate::mmap::MapVariant::Rw(lock) => {
107 let guard = lock.read();
108 guard.as_ptr()
109 }
110 crate::mmap::MapVariant::Cow(m) => m.as_ptr(),
111 };
112
113 let addr = unsafe { ptr.add(start) };
115
116 #[cfg(unix)]
117 {
118 let result = unsafe { libc::munlock(addr as *const libc::c_void, length) };
120
121 if result != 0 {
122 let err = std::io::Error::last_os_error();
123 return Err(MmapIoError::UnlockFailed(format!("munlock failed: {err}")));
124 }
125 }
126
127 #[cfg(windows)]
128 {
129 extern "system" {
130 fn VirtualUnlock(lpAddress: *const core::ffi::c_void, dwSize: usize) -> i32;
131 }
132
133 let result = unsafe { VirtualUnlock(addr as *const core::ffi::c_void, length) };
135
136 if result == 0 {
137 let err = std::io::Error::last_os_error();
138 let err_code = err.raw_os_error().unwrap_or(0);
140 if err_code != 158 {
141 return Err(MmapIoError::UnlockFailed(format!(
143 "VirtualUnlock failed: {err}"
144 )));
145 }
146 }
147 }
148
149 Ok(())
150 }
151
152 #[cfg(feature = "locking")]
160 pub fn lock_all(&self) -> Result<()> {
161 let len = self.current_len()?;
162 self.lock(0, len)
163 }
164
165 #[cfg(feature = "locking")]
173 pub fn unlock_all(&self) -> Result<()> {
174 let len = self.current_len()?;
175 self.unlock(0, len)
176 }
177}
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182 use crate::create_mmap;
183 use std::fs;
184 use std::path::PathBuf;
185
186 fn tmp_path(name: &str) -> PathBuf {
187 let mut p = std::env::temp_dir();
188 p.push(format!("mmap_io_lock_test_{}_{}", name, std::process::id()));
189 p
190 }
191
192 #[test]
193 #[cfg(feature = "locking")]
194 fn test_lock_unlock_operations() {
195 let path = tmp_path("lock_ops");
196 let _ = fs::remove_file(&path);
197
198 let mmap = create_mmap(&path, 8192).expect("create");
199
200 let lock_result = mmap.lock(0, 4096);
205 if lock_result.is_ok() {
206 mmap.unlock(0, 4096)
208 .expect("unlock should succeed after lock");
209 } else {
210 println!("Lock failed (expected without privileges): {lock_result:?}");
212 }
213
214 mmap.lock(0, 0).expect("empty lock");
216 mmap.unlock(0, 0).expect("empty unlock");
217
218 assert!(mmap.lock(8192, 1).is_err());
220 assert!(mmap.unlock(8192, 1).is_err());
221
222 let lock_all_result = mmap.lock_all();
224 if lock_all_result.is_ok() {
225 mmap.unlock_all()
226 .expect("unlock_all should succeed after lock_all");
227 }
228
229 fs::remove_file(&path).expect("cleanup");
230 }
231
232 #[test]
233 #[cfg(feature = "locking")]
234 fn test_lock_with_different_modes() {
235 let path = tmp_path("lock_modes");
236 let _ = fs::remove_file(&path);
237
238 let mmap = create_mmap(&path, 4096).expect("create");
240 let _ = mmap.lock(0, 1024); drop(mmap);
242
243 let mmap = MemoryMappedFile::open_ro(&path).expect("open ro");
245 let _ = mmap.lock(0, 1024); #[cfg(feature = "cow")]
248 {
249 let mmap = MemoryMappedFile::open_cow(&path).expect("open cow");
251 let _ = mmap.lock(0, 1024); }
253
254 fs::remove_file(&path).expect("cleanup");
255 }
256
257 #[test]
258 #[cfg(all(feature = "locking", unix))]
259 fn test_multiple_lock_regions() {
260 let path = tmp_path("multi_lock");
261 let _ = fs::remove_file(&path);
262
263 let mmap = create_mmap(&path, 16384).expect("create");
264
265 let _ = mmap.lock(0, 4096);
268 let _ = mmap.lock(4096, 4096);
269 let _ = mmap.lock(8192, 4096);
270
271 let _ = mmap.unlock(4096, 4096);
273 let _ = mmap.unlock(0, 4096);
274 let _ = mmap.unlock(8192, 4096);
275
276 fs::remove_file(&path).expect("cleanup");
277 }
278}