1use std::fs::File;
2use std::io;
3use std::mem::ManuallyDrop;
4use std::os::unix::io::{FromRawFd, RawFd};
5use std::sync::atomic::{AtomicUsize, Ordering};
6
7#[cfg(any(
8 all(target_os = "linux", not(target_arch = "mips")),
9 target_os = "freebsd",
10 target_os = "android"
11))]
12const MAP_STACK: libc::c_int = libc::MAP_STACK;
13
14#[cfg(not(any(
15 all(target_os = "linux", not(target_arch = "mips")),
16 target_os = "freebsd",
17 target_os = "android"
18)))]
19const MAP_STACK: libc::c_int = 0;
20
21#[cfg(any(target_os = "linux", target_os = "android"))]
22const MAP_POPULATE: libc::c_int = libc::MAP_POPULATE;
23
24#[cfg(not(any(target_os = "linux", target_os = "android")))]
25const MAP_POPULATE: libc::c_int = 0;
26
27#[cfg(any(target_os = "linux", target_os = "android"))]
28const MAP_HUGETLB: libc::c_int = libc::MAP_HUGETLB;
29
30#[cfg(target_os = "linux")]
31const MAP_HUGE_MASK: libc::c_int = libc::MAP_HUGE_MASK;
32
33#[cfg(any(target_os = "linux", target_os = "android"))]
34const MAP_HUGE_SHIFT: libc::c_int = libc::MAP_HUGE_SHIFT;
35
36#[cfg(not(any(target_os = "linux", target_os = "android")))]
37const MAP_HUGETLB: libc::c_int = 0;
38
39#[cfg(not(target_os = "linux"))]
40const MAP_HUGE_MASK: libc::c_int = 0;
41
42#[cfg(not(any(target_os = "linux", target_os = "android")))]
43const MAP_HUGE_SHIFT: libc::c_int = 0;
44
45#[cfg(any(
46 target_os = "linux",
47 target_os = "android",
48 target_vendor = "apple",
49 target_os = "netbsd",
50 target_os = "solaris",
51 target_os = "illumos",
52))]
53const MAP_NORESERVE: libc::c_int = libc::MAP_NORESERVE;
54
55#[cfg(not(any(
56 target_os = "linux",
57 target_os = "android",
58 target_vendor = "apple",
59 target_os = "netbsd",
60 target_os = "solaris",
61 target_os = "illumos",
62)))]
63const MAP_NORESERVE: libc::c_int = 0;
64
65#[cfg(any(
66 target_os = "android",
67 all(
68 target_os = "linux",
69 not(any(target_env = "musl", target_env = "ohos"))
70 )
71))]
72use libc::{mmap64 as mmap, off64_t as off_t};
73
74#[cfg(not(any(
75 target_os = "android",
76 all(
77 target_os = "linux",
78 not(any(target_env = "musl", target_env = "ohos"))
79 )
80)))]
81use libc::{mmap, off_t};
82
83pub struct MmapInner {
84 ptr: *mut libc::c_void,
85 len: usize,
86}
87
88impl MmapInner {
89 fn new(
93 len: usize,
94 prot: libc::c_int,
95 flags: libc::c_int,
96 file: RawFd,
97 offset: u64,
98 ) -> io::Result<MmapInner> {
99 let alignment = offset % page_size() as u64;
100 let aligned_offset = offset - alignment;
101
102 let (map_len, map_offset) = Self::adjust_mmap_params(len, alignment as usize)?;
103
104 let ptr = unsafe {
107 mmap(
108 std::ptr::null_mut(),
109 map_len as libc::size_t,
110 prot,
111 flags,
112 file,
113 aligned_offset as off_t,
114 )
115 };
116
117 if ptr == libc::MAP_FAILED {
118 Err(io::Error::last_os_error())
119 } else {
120 Ok(unsafe { Self::from_raw_parts(ptr, len, map_offset) })
123 }
124 }
125
126 fn adjust_mmap_params(len: usize, alignment: usize) -> io::Result<(usize, usize)> {
127 if std::mem::size_of::<usize>() < 8 && len > isize::MAX as usize {
137 return Err(io::Error::new(
138 io::ErrorKind::InvalidData,
139 "memory map length overflows isize",
140 ));
141 }
142
143 let map_len = len + alignment;
144 let map_offset = alignment;
145
146 let map_len = map_len.max(1);
153
154 Ok((map_len, map_offset))
178 }
179
180 fn as_mmap_params(&self) -> (*mut libc::c_void, usize, usize) {
185 let offset = self.ptr as usize % page_size();
186 let len = self.len + offset;
187
188 if len == 0 {
222 (self.ptr, 1, 0)
223 } else {
224 let offset = self.ptr as usize % page_size();
225 let ptr = unsafe { self.ptr.sub(offset) };
228 (ptr, len, offset)
229 }
230 }
231
232 unsafe fn from_raw_parts(ptr: *mut libc::c_void, len: usize, offset: usize) -> Self {
242 debug_assert_eq!(ptr as usize % page_size(), 0, "ptr not page-aligned");
243 debug_assert!(offset < page_size(), "offset larger than page size");
244
245 Self {
246 ptr: unsafe { ptr.add(offset) },
247 len,
248 }
249 }
250
251 pub fn map(
252 len: usize,
253 file: RawFd,
254 offset: u64,
255 populate: bool,
256 no_reserve: bool,
257 _probe_handle: bool,
258 ) -> io::Result<MmapInner> {
259 let populate = if populate { MAP_POPULATE } else { 0 };
260 let no_reserve = if no_reserve { MAP_NORESERVE } else { 0 };
261 MmapInner::new(
262 len,
263 libc::PROT_READ,
264 libc::MAP_SHARED | populate | no_reserve,
265 file,
266 offset,
267 )
268 }
269
270 pub fn map_exec(
271 len: usize,
272 file: RawFd,
273 offset: u64,
274 populate: bool,
275 no_reserve: bool,
276 _probe_handle: bool,
277 ) -> io::Result<MmapInner> {
278 let populate = if populate { MAP_POPULATE } else { 0 };
279 let no_reserve = if no_reserve { MAP_NORESERVE } else { 0 };
280 MmapInner::new(
281 len,
282 libc::PROT_READ | libc::PROT_EXEC,
283 libc::MAP_SHARED | populate | no_reserve,
284 file,
285 offset,
286 )
287 }
288
289 pub fn map_mut(
290 len: usize,
291 file: RawFd,
292 offset: u64,
293 populate: bool,
294 no_reserve: bool,
295 _probe_handle: bool,
296 ) -> io::Result<MmapInner> {
297 let populate = if populate { MAP_POPULATE } else { 0 };
298 let no_reserve = if no_reserve { MAP_NORESERVE } else { 0 };
299 MmapInner::new(
300 len,
301 libc::PROT_READ | libc::PROT_WRITE,
302 libc::MAP_SHARED | populate | no_reserve,
303 file,
304 offset,
305 )
306 }
307
308 pub fn map_copy(
309 len: usize,
310 file: RawFd,
311 offset: u64,
312 populate: bool,
313 no_reserve: bool,
314 _probe_handle: bool,
315 ) -> io::Result<MmapInner> {
316 let populate = if populate { MAP_POPULATE } else { 0 };
317 let no_reserve = if no_reserve { MAP_NORESERVE } else { 0 };
318 MmapInner::new(
319 len,
320 libc::PROT_READ | libc::PROT_WRITE,
321 libc::MAP_PRIVATE | populate | no_reserve,
322 file,
323 offset,
324 )
325 }
326
327 pub fn map_copy_read_only(
328 len: usize,
329 file: RawFd,
330 offset: u64,
331 populate: bool,
332 no_reserve: bool,
333 _probe_handle: bool,
334 ) -> io::Result<MmapInner> {
335 let populate = if populate { MAP_POPULATE } else { 0 };
336 let no_reserve = if no_reserve { MAP_NORESERVE } else { 0 };
337 MmapInner::new(
338 len,
339 libc::PROT_READ,
340 libc::MAP_PRIVATE | populate | no_reserve,
341 file,
342 offset,
343 )
344 }
345
346 pub fn map_anon(
348 len: usize,
349 stack: bool,
350 populate: bool,
351 huge: Option<u8>,
352 no_reserve: bool,
353 ) -> io::Result<MmapInner> {
354 let stack = if stack { MAP_STACK } else { 0 };
355 let populate = if populate { MAP_POPULATE } else { 0 };
356 let hugetlb = if huge.is_some() { MAP_HUGETLB } else { 0 };
357 let hugetlb_size = huge.map_or(0, |mask| {
358 (u64::from(mask) & (MAP_HUGE_MASK as u64)) << MAP_HUGE_SHIFT
359 }) as i32;
360 let no_reserve = if no_reserve { MAP_NORESERVE } else { 0 };
361 MmapInner::new(
362 len,
363 libc::PROT_READ | libc::PROT_WRITE,
364 libc::MAP_PRIVATE
365 | libc::MAP_ANON
366 | stack
367 | populate
368 | hugetlb
369 | hugetlb_size
370 | no_reserve,
371 -1,
372 0,
373 )
374 }
375
376 pub fn flush(&self, offset: usize, len: usize) -> io::Result<()> {
377 if offset > self.len || len > self.len - offset {
378 return Err(io::ErrorKind::InvalidInput.into());
379 }
380 let alignment = (self.ptr as usize + offset) % page_size();
381 let offset = offset as isize - alignment as isize;
382 let len = len + alignment;
383 let result =
384 unsafe { libc::msync(self.ptr.offset(offset), len as libc::size_t, libc::MS_SYNC) };
386 if result == 0 {
387 Ok(())
388 } else {
389 Err(io::Error::last_os_error())
390 }
391 }
392
393 pub fn flush_async(&self, offset: usize, len: usize) -> io::Result<()> {
394 if offset > self.len || len > self.len - offset {
395 return Err(io::ErrorKind::InvalidInput.into());
396 }
397 let alignment = (self.ptr as usize + offset) % page_size();
398 let offset = offset as isize - alignment as isize;
399 let len = len + alignment;
400 let result =
401 unsafe { libc::msync(self.ptr.offset(offset), len as libc::size_t, libc::MS_ASYNC) };
403 if result == 0 {
404 Ok(())
405 } else {
406 Err(io::Error::last_os_error())
407 }
408 }
409
410 fn mprotect(&mut self, prot: libc::c_int) -> io::Result<()> {
411 let alignment = self.ptr as usize % page_size();
412 let ptr = unsafe { self.ptr.sub(alignment) };
414 let len = self.len + alignment;
415 let len = len.max(1);
416
417 if unsafe { libc::mprotect(ptr, len, prot) } == 0 {
419 Ok(())
420 } else {
421 Err(io::Error::last_os_error())
422 }
423 }
424
425 pub fn make_read_only(&mut self) -> io::Result<()> {
426 self.mprotect(libc::PROT_READ)
427 }
428
429 pub fn make_exec(&mut self) -> io::Result<()> {
430 self.mprotect(libc::PROT_READ | libc::PROT_EXEC)
431 }
432
433 pub fn make_mut(&mut self) -> io::Result<()> {
434 self.mprotect(libc::PROT_READ | libc::PROT_WRITE)
435 }
436
437 #[inline]
438 pub fn ptr(&self) -> *const u8 {
439 self.ptr as *const u8
440 }
441
442 #[inline]
443 pub fn mut_ptr(&mut self) -> *mut u8 {
444 self.ptr.cast()
445 }
446
447 #[inline]
448 pub fn len(&self) -> usize {
449 self.len
450 }
451
452 pub unsafe fn advise(&self, advice: libc::c_int, offset: usize, len: usize) -> io::Result<()> {
459 if offset > self.len || len > self.len {
460 return Err(std::io::ErrorKind::InvalidInput.into());
461 }
462 let alignment = (self.ptr as usize + offset) % page_size();
463 let offset = offset as isize - alignment as isize;
464 let len = len + alignment;
465
466 let ptr = unsafe { self.ptr.offset(offset) };
468
469 #[cfg(target_os = "aix")]
473 let ptr = self.ptr.offset(offset).cast();
474
475 if unsafe { libc::madvise(ptr, len, advice) } != 0 {
477 Err(io::Error::last_os_error())
478 } else {
479 Ok(())
480 }
481 }
482
483 #[cfg(target_os = "linux")]
484 pub fn remap(&mut self, new_len: usize, options: crate::RemapOptions) -> io::Result<()> {
485 let (old_ptr, old_len, offset) = self.as_mmap_params();
486 let (map_len, offset) = Self::adjust_mmap_params(new_len, offset)?;
487
488 let new_ptr = unsafe { libc::mremap(old_ptr, old_len, map_len, options.into_flags()) };
490
491 if new_ptr == libc::MAP_FAILED {
492 Err(io::Error::last_os_error())
493 } else {
494 let new_map = unsafe { Self::from_raw_parts(new_ptr, new_len, offset) };
496 let old_map = std::mem::replace(self, new_map);
499 std::mem::forget(old_map);
500 Ok(())
501 }
502 }
503
504 pub fn lock(&self) -> io::Result<()> {
505 unsafe {
506 if libc::mlock(self.ptr, self.len) != 0 {
507 Err(io::Error::last_os_error())
508 } else {
509 Ok(())
510 }
511 }
512 }
513
514 pub fn unlock(&self) -> io::Result<()> {
515 unsafe {
516 if libc::munlock(self.ptr, self.len) != 0 {
517 Err(io::Error::last_os_error())
518 } else {
519 Ok(())
520 }
521 }
522 }
523
524 #[cfg(not(any(target_os = "linux", target_os = "android", target_os = "freebsd")))]
525 #[allow(clippy::unnecessary_wraps)]
526 pub fn check_safe_to_map(
527 _fd: RawFd,
528 _offset: u64,
529 _len: usize,
530 ) -> Result<(), crate::MapIfSafeError> {
531 Err(crate::MapIfSafeError::new_not_supported())
532 }
533
534 #[cfg(any(target_os = "linux", target_os = "android", target_os = "freebsd"))]
536 pub fn check_safe_to_map(
537 fd: RawFd,
538 offset: u64,
539 len: usize,
540 ) -> Result<(), crate::MapIfSafeError> {
541 Self::check_fd_safe_to_map(fd)?;
543
544 let file_size = file_len(fd).map_err(|e| {
546 crate::MapIfSafeError::new_not_safe(io::Error::new(
547 io::ErrorKind::InvalidInput,
548 format!("fd is not safe to map: failed to determine file size: {e}"),
549 ))
550 })?;
551
552 let end = offset.checked_add(len as u64).ok_or_else(|| {
553 crate::MapIfSafeError::new_map_failed(io::Error::new(
554 io::ErrorKind::InvalidInput,
555 "mapping region overflows u64",
556 ))
557 })?;
558 if end > file_size {
559 return Err(crate::MapIfSafeError::new_not_safe(io::Error::new(
560 io::ErrorKind::InvalidInput,
561 format!("map request is not safe: offset + length ({end}) exceeds the size of the file ({file_size})"),
562 )));
563 }
564
565 Ok(())
566 }
567
568 #[cfg(any(target_os = "linux", target_os = "android", target_os = "freebsd"))]
573 fn check_fd_safe_to_map(fd: RawFd) -> Result<(), crate::MapIfSafeError> {
574 if let Some(seals) = Self::get_memfd_seals(fd) {
576 return Self::check_memfd_seals(seals);
577 }
578
579 #[cfg(target_os = "freebsd")]
581 return Err(crate::MapIfSafeError::new_not_safe(io::Error::new(
582 io::ErrorKind::InvalidInput,
583 "fd is not safe to map: it is not a memfd",
584 )));
585
586 #[cfg(any(target_os = "linux", target_os = "android"))]
588 if Self::has_fs_verity(fd) {
589 Ok(())
590 } else {
591 Err(crate::MapIfSafeError::new_not_safe(io::Error::new(
592 io::ErrorKind::InvalidInput,
593 "fd is not safe to map: it is not a memfd and it has no fs-verity",
594 )))
595 }
596 }
597
598 #[cfg(any(target_os = "linux", target_os = "android", target_os = "freebsd"))]
600 fn get_memfd_seals(fd: RawFd) -> Option<libc::c_int> {
601 let seals = unsafe { libc::fcntl(fd, libc::F_GET_SEALS) };
603 if seals >= 0 {
604 Some(seals)
605 } else {
606 None
607 }
608 }
609
610 #[cfg(any(target_os = "linux", target_os = "android", target_os = "freebsd"))]
612 fn check_memfd_seals(seals: libc::c_int) -> Result<(), crate::MapIfSafeError> {
613 let required = libc::F_SEAL_SHRINK | libc::F_SEAL_WRITE;
614 let overlap = seals & required;
615 if overlap == required {
616 Ok(())
617 } else {
618 let error = match overlap {
619 libc::F_SEAL_SHRINK => "fd is not safe to map: missing seals F_SEAL_WRITE",
620 libc::F_SEAL_WRITE => "fd is not safe to map: missing seals F_SEAL_SHRINK",
621 _ => "fd is not safe to map: missing seals F_SEAL_SHRINK and F_SEAL_WRITE",
622 };
623 Err(crate::MapIfSafeError::new_not_safe(io::Error::new(
624 io::ErrorKind::InvalidInput,
625 error,
626 )))
627 }
628 }
629
630 #[cfg(any(target_os = "linux", target_os = "android"))]
631 fn has_fs_verity(fd: RawFd) -> bool {
633 #[repr(C)]
634 struct FsVerityDigest {
635 digest_algorithm: u16,
636 digest_size: u16,
637 }
638 const FS_IOC_MEASURE_VERITY: libc::c_ulong = 0xC004_6686;
639
640 let mut digest = std::mem::MaybeUninit::<FsVerityDigest>::zeroed();
641 #[allow(clippy::cast_possible_wrap)]
642 let ret = unsafe { libc::ioctl(fd, FS_IOC_MEASURE_VERITY as _, digest.as_mut_ptr()) };
643 ret == 0 || io::Error::last_os_error().raw_os_error() == Some(libc::EOVERFLOW)
644 }
645}
646
647impl Drop for MmapInner {
648 fn drop(&mut self) {
649 let (ptr, len, _) = self.as_mmap_params();
650
651 unsafe { libc::munmap(ptr, len as libc::size_t) };
655 }
656}
657
658unsafe impl Sync for MmapInner {}
659unsafe impl Send for MmapInner {}
660
661fn page_size() -> usize {
662 static PAGE_SIZE: AtomicUsize = AtomicUsize::new(0);
663
664 match PAGE_SIZE.load(Ordering::Relaxed) {
665 0 => {
666 let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize };
667
668 PAGE_SIZE.store(page_size, Ordering::Relaxed);
669
670 page_size
671 }
672 page_size => page_size,
673 }
674}
675
676pub fn file_len(file: RawFd) -> io::Result<u64> {
677 unsafe {
680 let file = ManuallyDrop::new(File::from_raw_fd(file));
681 Ok(file.metadata()?.len())
682 }
683}