1use std::fs::File;
6use std::io;
7use std::ptr;
8
9#[cfg(target_os = "linux")]
10use std::os::unix::io::AsRawFd;
11
12#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
14const SYS_MMAP: i64 = 9;
15#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
16const SYS_MUNMAP: i64 = 11;
17#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
18const SYS_MSYNC: i64 = 26;
19#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
20const SYS_MADVISE: i64 = 28;
21#[cfg(target_os = "linux")]
22const PROT_READ: i32 = 1;
23#[cfg(target_os = "linux")]
24const PROT_WRITE: i32 = 2;
25#[cfg(target_os = "linux")]
26const MAP_SHARED: i32 = 1;
27#[cfg(target_os = "linux")]
28const MAP_FAILED: isize = -1;
29#[cfg(target_os = "linux")]
30const MS_SYNC: i32 = 4;
31#[cfg(target_os = "linux")]
32const MS_ASYNC: i32 = 1;
33#[cfg(target_os = "linux")]
34const MADV_NORMAL: i32 = 0;
35#[cfg(target_os = "linux")]
36const MADV_RANDOM: i32 = 1;
37#[cfg(target_os = "linux")]
38const MADV_SEQUENTIAL: i32 = 2;
39#[cfg(target_os = "linux")]
40const MADV_WILLNEED: i32 = 3;
41#[cfg(target_os = "linux")]
42const MADV_DONTNEED: i32 = 4;
43
44#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
46macro_rules! syscall {
47 ($num:expr, $arg1:expr, $arg2:expr) => {{
48 let mut ret: i64;
49 unsafe {
50 std::arch::asm!(
51 "syscall",
52 inlateout("rax") $num as i64 => ret,
53 in("rdi") $arg1 as i64,
54 in("rsi") $arg2 as i64,
55 lateout("rcx") _,
56 lateout("r11") _,
57 options(nostack)
58 );
59 }
60 ret
61 }};
62 ($num:expr, $arg1:expr, $arg2:expr, $arg3:expr) => {{
63 let mut ret: i64;
64 unsafe {
65 std::arch::asm!(
66 "syscall",
67 inlateout("rax") $num as i64 => ret,
68 in("rdi") $arg1 as i64,
69 in("rsi") $arg2 as i64,
70 in("rdx") $arg3 as i64,
71 lateout("rcx") _,
72 lateout("r11") _,
73 options(nostack)
74 );
75 }
76 ret
77 }};
78 ($num:expr, $arg1:expr, $arg2:expr, $arg3:expr, $arg4:expr, $arg5:expr, $arg6:expr) => {{
79 let mut ret: i64;
80 unsafe {
81 std::arch::asm!(
82 "syscall",
83 inlateout("rax") $num as i64 => ret,
84 in("rdi") $arg1 as i64,
85 in("rsi") $arg2 as i64,
86 in("rdx") $arg3 as i64,
87 in("r10") $arg4 as i64,
88 in("r8") $arg5 as i64,
89 in("r9") $arg6 as i64,
90 lateout("rcx") _,
91 lateout("r11") _,
92 options(nostack)
93 );
94 }
95 ret
96 }};
97}
98
99#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
100unsafe fn linux_mmap(
101 addr: *mut u8,
102 len: usize,
103 prot: i32,
104 flags: i32,
105 fd: i32,
106 offset: i64,
107) -> isize {
108 syscall!(SYS_MMAP, addr, len, prot, flags, fd, offset) as isize
109}
110
111#[cfg(all(target_os = "linux", not(target_arch = "x86_64")))]
112unsafe fn linux_mmap(
113 _addr: *mut u8,
114 _len: usize,
115 _prot: i32,
116 _flags: i32,
117 _fd: i32,
118 _offset: i64,
119) -> isize {
120 -1 }
122
123#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
124unsafe fn linux_msync(addr: *mut u8, len: usize, flags: i32) -> i64 {
125 syscall!(SYS_MSYNC, addr, len, flags)
126}
127
128#[cfg(all(target_os = "linux", not(target_arch = "x86_64")))]
129unsafe fn linux_msync(_addr: *mut u8, _len: usize, _flags: i32) -> i64 {
130 -1
131}
132
133#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
134unsafe fn linux_madvise(addr: *mut u8, len: usize, advice: i32) -> i64 {
135 syscall!(SYS_MADVISE, addr, len, advice)
136}
137
138#[cfg(all(target_os = "linux", not(target_arch = "x86_64")))]
139unsafe fn linux_madvise(_addr: *mut u8, _len: usize, _advice: i32) -> i64 {
140 -1
141}
142
143#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
144unsafe fn linux_munmap(addr: *mut u8, len: usize) -> i64 {
145 syscall!(SYS_MUNMAP, addr, len)
146}
147
148#[cfg(all(target_os = "linux", not(target_arch = "x86_64")))]
149unsafe fn linux_munmap(_addr: *mut u8, _len: usize) -> i64 {
150 -1
151}
152
153#[derive(Debug, Clone, Copy)]
155pub enum MadviseAdvice {
156 Normal, Random, Sequential, WillNeed, DontNeed, }
162
163#[cfg(target_os = "linux")]
164pub struct MmapFile {
165 ptr: *mut u8,
166 len: usize,
167 writable: bool,
168 _file: File,
169}
170
171#[cfg(target_os = "linux")]
172impl MmapFile {
173 pub fn new(file: File, len: usize) -> io::Result<Self> {
175 let fd = file.as_raw_fd();
176
177 let ptr = unsafe { linux_mmap(ptr::null_mut::<u8>(), len, PROT_READ, MAP_SHARED, fd, 0) };
179
180 if ptr == MAP_FAILED {
181 return Err(io::Error::last_os_error());
182 }
183
184 Ok(Self {
185 ptr: ptr as *mut u8,
186 len,
187 writable: false,
188 _file: file,
189 })
190 }
191
192 pub fn new_mut(file: File, len: usize) -> io::Result<Self> {
194 let fd = file.as_raw_fd();
195
196 let ptr = unsafe {
197 linux_mmap(
198 ptr::null_mut::<u8>(),
199 len,
200 PROT_READ | PROT_WRITE,
201 MAP_SHARED,
202 fd,
203 0,
204 )
205 };
206
207 if ptr == MAP_FAILED {
208 return Err(io::Error::last_os_error());
209 }
210
211 Ok(Self {
212 ptr: ptr as *mut u8,
213 len,
214 writable: true,
215 _file: file,
216 })
217 }
218
219 pub fn as_slice(&self) -> &[u8] {
221 unsafe { std::slice::from_raw_parts(self.ptr, self.len) }
222 }
223
224 pub fn as_mut_slice(&mut self) -> io::Result<&mut [u8]> {
226 if !self.writable {
227 return Err(io::Error::new(
228 io::ErrorKind::PermissionDenied,
229 "Mmap is read-only",
230 ));
231 }
232 Ok(unsafe { std::slice::from_raw_parts_mut(self.ptr, self.len) })
233 }
234
235 pub fn read_u32(&self, offset: usize) -> io::Result<u32> {
237 if offset + 4 > self.len {
238 return Err(io::Error::new(
239 io::ErrorKind::UnexpectedEof,
240 "Offset out of bounds",
241 ));
242 }
243
244 let bytes = unsafe { std::slice::from_raw_parts(self.ptr.add(offset), 4) };
245
246 Ok(u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
247 }
248
249 pub fn read_bytes(&self, offset: usize, len: usize) -> io::Result<&[u8]> {
251 if offset + len > self.len {
252 return Err(io::Error::new(
253 io::ErrorKind::UnexpectedEof,
254 "Offset out of bounds",
255 ));
256 }
257
258 Ok(unsafe { std::slice::from_raw_parts(self.ptr.add(offset), len) })
259 }
260
261 pub fn write_bytes(&mut self, offset: usize, data: &[u8]) -> io::Result<()> {
263 if !self.writable {
264 return Err(io::Error::new(
265 io::ErrorKind::PermissionDenied,
266 "Mmap is read-only",
267 ));
268 }
269
270 if offset + data.len() > self.len {
271 return Err(io::Error::new(
272 io::ErrorKind::UnexpectedEof,
273 "Write out of bounds",
274 ));
275 }
276
277 unsafe {
278 ptr::copy_nonoverlapping(data.as_ptr(), self.ptr.add(offset), data.len());
279 }
280
281 Ok(())
282 }
283
284 pub fn read_struct<T: Copy>(&self, offset: usize) -> io::Result<&T> {
286 let size = std::mem::size_of::<T>();
287 if offset + size > self.len {
288 return Err(io::Error::new(
289 io::ErrorKind::UnexpectedEof,
290 "Struct read out of bounds",
291 ));
292 }
293
294 unsafe { Ok(&*(self.ptr.add(offset) as *const T)) }
295 }
296
297 pub fn read_struct_mut<T: Copy>(&mut self, offset: usize) -> io::Result<&mut T> {
299 if !self.writable {
300 return Err(io::Error::new(
301 io::ErrorKind::PermissionDenied,
302 "Mmap is read-only",
303 ));
304 }
305
306 let size = std::mem::size_of::<T>();
307 if offset + size > self.len {
308 return Err(io::Error::new(
309 io::ErrorKind::UnexpectedEof,
310 "Struct read out of bounds",
311 ));
312 }
313
314 unsafe { Ok(&mut *(self.ptr.add(offset) as *mut T)) }
315 }
316
317 pub fn flush(&self) -> io::Result<()> {
319 if !self.writable {
320 return Ok(()); }
322
323 let result = unsafe { linux_msync(self.ptr, self.len, MS_SYNC) };
324
325 if result < 0 {
326 return Err(io::Error::last_os_error());
327 }
328
329 Ok(())
330 }
331
332 pub fn flush_async(&self) -> io::Result<()> {
334 if !self.writable {
335 return Ok(()); }
337
338 let result = unsafe { linux_msync(self.ptr, self.len, MS_ASYNC) };
339
340 if result < 0 {
341 return Err(io::Error::last_os_error());
342 }
343
344 Ok(())
345 }
346
347 pub fn advise(&self, advice: MadviseAdvice) -> io::Result<()> {
349 let advice_flag = match advice {
350 MadviseAdvice::Normal => MADV_NORMAL,
351 MadviseAdvice::Random => MADV_RANDOM,
352 MadviseAdvice::Sequential => MADV_SEQUENTIAL,
353 MadviseAdvice::WillNeed => MADV_WILLNEED,
354 MadviseAdvice::DontNeed => MADV_DONTNEED,
355 };
356
357 let result = unsafe { linux_madvise(self.ptr, self.len, advice_flag) };
358
359 if result < 0 {
360 return Err(io::Error::last_os_error());
361 }
362
363 Ok(())
364 }
365
366 pub fn len(&self) -> usize {
368 self.len
369 }
370
371 pub fn is_empty(&self) -> bool {
373 self.len == 0
374 }
375}
376
377#[cfg(target_os = "linux")]
378impl Drop for MmapFile {
379 fn drop(&mut self) {
380 let _ = unsafe { linux_munmap(self.ptr, self.len) };
381 }
382}
383
384#[cfg(not(target_os = "linux"))]
386pub struct MmapFile {
387 _placeholder: (),
388}
389
390#[cfg(not(target_os = "linux"))]
391impl MmapFile {
392 pub fn new(_file: File, _len: usize) -> io::Result<Self> {
393 Err(io::Error::new(
394 io::ErrorKind::Unsupported,
395 "mmap only supported on Linux (use regular file I/O)",
396 ))
397 }
398
399 pub fn as_slice(&self) -> &[u8] {
400 &[]
401 }
402
403 pub fn read_u32(&self, _offset: usize) -> io::Result<u32> {
404 Err(io::Error::new(
405 io::ErrorKind::Unsupported,
406 "Not implemented",
407 ))
408 }
409
410 pub fn read_bytes(&self, _offset: usize, _len: usize) -> io::Result<&[u8]> {
411 Err(io::Error::new(
412 io::ErrorKind::Unsupported,
413 "Not implemented",
414 ))
415 }
416}
417
418#[cfg(test)]
419mod tests {
420 use super::*;
421 use std::fs::OpenOptions;
422 use std::io::Write;
423
424 #[test]
425 #[cfg(target_os = "linux")]
426 fn test_mmap_basic() {
427 let path = "/tmp/mmap_test.dat";
428
429 let mut file = OpenOptions::new()
431 .write(true)
432 .create(true)
433 .truncate(true)
434 .open(path)
435 .unwrap();
436
437 file.write_all(b"Hello, mmap!").unwrap();
438 drop(file);
439
440 let file = OpenOptions::new().read(true).open(path).unwrap();
442 let len = file.metadata().unwrap().len() as usize;
443
444 let mmap = MmapFile::new(file, len).unwrap();
445 let data = mmap.as_slice();
446
447 assert_eq!(data, b"Hello, mmap!");
448
449 std::fs::remove_file(path).unwrap();
450 }
451}