arcbox_virtio_blk/
mmap.rs1pub struct MmapBackend {
5 ptr: *mut u8,
7 size: usize,
9 read_only: bool,
11}
12
13unsafe impl Send for MmapBackend {}
17unsafe impl Sync for MmapBackend {}
19
20impl MmapBackend {
21 pub fn new(path: &std::path::Path, read_only: bool) -> std::io::Result<Self> {
27 let file = std::fs::OpenOptions::new()
28 .read(true)
29 .write(!read_only)
30 .open(path)?;
31
32 let metadata = file.metadata()?;
33 let size = metadata.len() as usize;
34
35 if size == 0 {
36 return Err(std::io::Error::new(
37 std::io::ErrorKind::InvalidInput,
38 "Cannot mmap empty file",
39 ));
40 }
41
42 use std::os::unix::io::AsRawFd;
43 let fd = file.as_raw_fd();
44
45 let prot = if read_only {
46 libc::PROT_READ
47 } else {
48 libc::PROT_READ | libc::PROT_WRITE
49 };
50
51 let ptr = unsafe { libc::mmap(std::ptr::null_mut(), size, prot, libc::MAP_SHARED, fd, 0) };
54
55 if ptr == libc::MAP_FAILED {
56 return Err(std::io::Error::last_os_error());
57 }
58
59 tracing::info!(
60 "Memory-mapped {} at {:p}, size={}",
61 path.display(),
62 ptr,
63 size
64 );
65
66 Ok(Self {
67 ptr: ptr as *mut u8,
68 size,
69 read_only,
70 })
71 }
72
73 #[must_use]
75 pub const fn capacity(&self) -> u64 {
76 (self.size / 512) as u64
77 }
78
79 pub fn read(&self, offset: usize, buf: &mut [u8]) -> std::io::Result<usize> {
81 if offset >= self.size {
82 return Ok(0);
83 }
84
85 let len = buf.len().min(self.size - offset);
86 unsafe {
89 std::ptr::copy_nonoverlapping(self.ptr.add(offset), buf.as_mut_ptr(), len);
90 }
91 Ok(len)
92 }
93
94 pub fn write(&self, offset: usize, buf: &[u8]) -> std::io::Result<usize> {
96 if self.read_only {
97 return Err(std::io::Error::new(
98 std::io::ErrorKind::PermissionDenied,
99 "Mapping is read-only",
100 ));
101 }
102
103 if offset >= self.size {
104 return Ok(0);
105 }
106
107 let len = buf.len().min(self.size - offset);
108 unsafe {
111 std::ptr::copy_nonoverlapping(buf.as_ptr(), self.ptr.add(offset), len);
112 }
113 Ok(len)
114 }
115
116 pub fn sync(&self) -> std::io::Result<()> {
118 let ret = unsafe { libc::msync(self.ptr as *mut libc::c_void, self.size, libc::MS_SYNC) };
120 if ret < 0 {
121 Err(std::io::Error::last_os_error())
122 } else {
123 Ok(())
124 }
125 }
126
127 #[must_use]
133 pub const unsafe fn as_ptr(&self) -> *const u8 {
134 self.ptr
135 }
136
137 #[must_use]
144 pub const unsafe fn as_mut_ptr(&self) -> *mut u8 {
145 self.ptr
146 }
147}
148
149impl Drop for MmapBackend {
150 fn drop(&mut self) {
151 if !self.ptr.is_null() {
152 unsafe {
154 libc::munmap(self.ptr as *mut libc::c_void, self.size);
155 }
156 }
157 }
158}
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163 use std::io::Write;
164 use tempfile::NamedTempFile;
165
166 #[test]
167 fn test_mmap_backend_creation() {
168 let mut temp_file = NamedTempFile::new().unwrap();
169 temp_file.write_all(&vec![0u8; 8192]).unwrap();
170
171 let backend = MmapBackend::new(temp_file.path(), false).unwrap();
172 assert_eq!(backend.capacity(), 16); }
174
175 #[test]
176 fn test_mmap_backend_read_write() {
177 let mut temp_file = NamedTempFile::new().unwrap();
178 temp_file.write_all(&vec![0u8; 4096]).unwrap();
179
180 let backend = MmapBackend::new(temp_file.path(), false).unwrap();
181
182 let write_data = b"MmapBackend test data!";
183 let written = backend.write(0, write_data).unwrap();
184 assert_eq!(written, write_data.len());
185
186 let mut read_data = vec![0u8; write_data.len()];
187 let read = backend.read(0, &mut read_data).unwrap();
188 assert_eq!(read, write_data.len());
189 assert_eq!(&read_data, write_data);
190 }
191
192 #[test]
193 fn test_mmap_backend_read_at_offset() {
194 let mut temp_file = NamedTempFile::new().unwrap();
195 let mut data = vec![0u8; 4096];
196 data[1024..1034].copy_from_slice(b"TestOffset");
197 temp_file.write_all(&data).unwrap();
198
199 let backend = MmapBackend::new(temp_file.path(), true).unwrap();
200
201 let mut buf = vec![0u8; 10];
202 backend.read(1024, &mut buf).unwrap();
203 assert_eq!(&buf, b"TestOffset");
204 }
205
206 #[test]
207 fn test_mmap_backend_read_only() {
208 let mut temp_file = NamedTempFile::new().unwrap();
209 temp_file.write_all(&vec![0u8; 4096]).unwrap();
210
211 let backend = MmapBackend::new(temp_file.path(), true).unwrap();
212
213 let result = backend.write(0, b"test");
214 assert!(result.is_err());
215 }
216
217 #[test]
218 fn test_mmap_backend_read_beyond_bounds() {
219 let mut temp_file = NamedTempFile::new().unwrap();
220 temp_file.write_all(&vec![0u8; 1024]).unwrap();
221
222 let backend = MmapBackend::new(temp_file.path(), true).unwrap();
223
224 let mut buf = vec![0u8; 100];
225 let read = backend.read(2000, &mut buf).unwrap();
226 assert_eq!(read, 0);
227 }
228
229 #[test]
230 fn test_mmap_backend_partial_read() {
231 let mut temp_file = NamedTempFile::new().unwrap();
232 temp_file.write_all(&vec![0xAA; 1024]).unwrap();
233
234 let backend = MmapBackend::new(temp_file.path(), true).unwrap();
235
236 let mut buf = vec![0u8; 100];
237 let read = backend.read(1000, &mut buf).unwrap();
238 assert_eq!(read, 24); }
240
241 #[test]
242 fn test_mmap_backend_sync() {
243 let mut temp_file = NamedTempFile::new().unwrap();
244 temp_file.write_all(&vec![0u8; 4096]).unwrap();
245
246 let backend = MmapBackend::new(temp_file.path(), false).unwrap();
247 backend.write(0, b"sync test").unwrap();
248
249 assert!(backend.sync().is_ok());
250 }
251
252 #[test]
253 fn test_mmap_backend_empty_file_fails() {
254 let temp_file = NamedTempFile::new().unwrap();
255
256 let result = MmapBackend::new(temp_file.path(), true);
257 assert!(result.is_err());
258 }
259}