| // Copyright 2023 The ChromiumOS Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| use std::fs::File; | |
| use std::io; | |
| use std::num::NonZeroUsize; | |
| use std::ops::Deref; | |
| use std::ops::DerefMut; | |
| use std::os::fd::AsRawFd; | |
| pub struct Mmap { | |
| file: File, | |
| ptr: *mut u8, | |
| /// size must be non-zero because zero as old_size on mremap(2) has special meaning. | |
| size: NonZeroUsize, | |
| } | |
| /// SAFETY: [Mmap] is safe to [Send] because the internal `ptr` is accessed by [Mmap] only. | |
| unsafe impl Send for Mmap {} | |
| impl Mmap { | |
| pub fn new(file: File, size: NonZeroUsize) -> io::Result<Self> { | |
| file.set_len(size.get() as u64)?; | |
| // SAFETY: Memory space managed by Rust is not modified. | |
| let ptr = unsafe { | |
| libc::mmap( | |
| std::ptr::null_mut(), | |
| size.get(), | |
| libc::PROT_READ | libc::PROT_WRITE, | |
| libc::MAP_SHARED, | |
| file.as_raw_fd(), | |
| 0, | |
| ) | |
| } as *mut u8; | |
| if ptr == libc::MAP_FAILED as *mut u8 { | |
| Err(io::Error::last_os_error()) | |
| } else { | |
| Ok(Self { file, ptr, size }) | |
| } | |
| } | |
| } | |
| impl Mmap { | |
| pub fn resize(&mut self, new_size: NonZeroUsize) -> io::Result<()> { | |
| self.file.set_len(new_size.get() as u64)?; | |
| // SAFETY: Memory space managed by Rust is not modified. | |
| let ptr = unsafe { | |
| libc::mremap( | |
| self.ptr as *mut libc::c_void, | |
| self.size.get(), | |
| new_size.get(), | |
| libc::MAP_SHARED, | |
| ) | |
| } as *mut u8; | |
| if ptr.is_null() { | |
| return Err(io::Error::last_os_error()); | |
| } | |
| self.ptr = ptr; | |
| self.size = new_size; | |
| Ok(()) | |
| } | |
| } | |
| impl Deref for Mmap { | |
| type Target = [u8]; | |
| fn deref(&self) -> &Self::Target { | |
| // SAFETY: ptr and size is guaranteed to be valid. | |
| // SAFETY: lifetime is the same as self. | |
| unsafe { std::slice::from_raw_parts(self.ptr, self.size.get()) } | |
| } | |
| } | |
| impl DerefMut for Mmap { | |
| fn deref_mut(&mut self) -> &mut Self::Target { | |
| // SAFETY: ptr and size is guaranteed to be valid. | |
| // SAFETY: lifetime is the same as self. | |
| unsafe { std::slice::from_raw_parts_mut(self.ptr, self.size.get()) } | |
| } | |
| } | |
| impl Drop for Mmap { | |
| fn drop(&mut self) { | |
| // SAFETY: ptr and size is guaranteed to be valid. | |
| let res = unsafe { libc::munmap(self.ptr as *mut libc::c_void, self.size.get()) }; | |
| if res != 0 { | |
| panic!("munmap failed: {}", io::Error::last_os_error()); | |
| } | |
| } | |
| } | |
| #[cfg(test)] | |
| mod tests { | |
| use std::io::Write; | |
| use super::*; | |
| #[test] | |
| fn test_mmap() { | |
| let mut file = tempfile::NamedTempFile::new().unwrap(); | |
| file.write_all(&[1; 2048]).unwrap(); | |
| let mut mmap = Mmap::new(file.reopen().unwrap(), NonZeroUsize::new(4096).unwrap()).unwrap(); | |
| assert_eq!(mmap.len(), 4096); | |
| assert_eq!(file.as_file().metadata().unwrap().len(), 4096); | |
| assert_eq!(&mmap[0..2048], &[1; 2048]); | |
| assert_eq!(&mmap[2048..4096], &[0; 2048]); | |
| mmap[3072..4096].copy_from_slice(&[2; 1024]); | |
| assert_eq!(&mmap[3072..4096], &[2; 1024]); | |
| // munmap(2) succeeds. | |
| drop(mmap); | |
| let mmap = Mmap::new(file.reopen().unwrap(), NonZeroUsize::new(8192).unwrap()).unwrap(); | |
| assert_eq!(mmap.len(), 8192); | |
| assert_eq!(file.as_file().metadata().unwrap().len(), 8192); | |
| assert_eq!(&mmap[0..2048], &[1; 2048]); | |
| assert_eq!(&mmap[2048..3072], &[0; 1024]); | |
| assert_eq!(&mmap[3072..4096], &[2; 1024]); | |
| assert_eq!(&mmap[4096..8192], &[0; 4096]); | |
| } | |
| #[test] | |
| fn test_mmap_non_page_size() { | |
| let mut file = tempfile::NamedTempFile::new().unwrap(); | |
| file.write_all(&[1; 10]).unwrap(); | |
| let mut mmap = Mmap::new(file.reopen().unwrap(), NonZeroUsize::new(20).unwrap()).unwrap(); | |
| assert_eq!(mmap.len(), 20); | |
| assert_eq!(file.as_file().metadata().unwrap().len(), 20); | |
| assert_eq!(&mmap[0..10], &[1; 10]); | |
| assert_eq!(&mmap[10..20], &[0; 10]); | |
| mmap[15..20].copy_from_slice(&[2; 5]); | |
| assert_eq!(&mmap[15..20], &[2; 5]); | |
| // munmap(2) succeeds. | |
| drop(mmap); | |
| let mmap = Mmap::new(file.reopen().unwrap(), NonZeroUsize::new(40).unwrap()).unwrap(); | |
| assert_eq!(mmap.len(), 40); | |
| assert_eq!(file.as_file().metadata().unwrap().len(), 40); | |
| assert_eq!(&mmap[0..10], &[1; 10]); | |
| assert_eq!(&mmap[10..15], &[0; 5]); | |
| assert_eq!(&mmap[15..20], &[2; 5]); | |
| assert_eq!(&mmap[20..40], &[0; 20]); | |
| } | |
| #[test] | |
| fn test_resize_extend() { | |
| let mut file = tempfile::NamedTempFile::new().unwrap(); | |
| file.write_all(&[1; 2048]).unwrap(); | |
| let mut mmap = Mmap::new(file.reopen().unwrap(), NonZeroUsize::new(4096).unwrap()).unwrap(); | |
| assert_eq!(mmap.len(), 4096); | |
| assert_eq!(file.as_file().metadata().unwrap().len(), 4096); | |
| mmap[3072..4096].copy_from_slice(&[2; 1024]); | |
| mmap.resize(NonZeroUsize::new(8192).unwrap()).unwrap(); | |
| assert_eq!(mmap.len(), 8192); | |
| assert_eq!(file.as_file().metadata().unwrap().len(), 8192); | |
| assert_eq!(&mmap[4096..8192], &[0; 4096]); | |
| mmap[4096..8192].copy_from_slice(&[3; 4096]); | |
| // munmap(2) succeeds. | |
| drop(mmap); | |
| let mmap = Mmap::new(file.reopen().unwrap(), NonZeroUsize::new(8192).unwrap()).unwrap(); | |
| assert_eq!(mmap.len(), 8192); | |
| assert_eq!(file.as_file().metadata().unwrap().len(), 8192); | |
| assert_eq!(&mmap[0..2048], &[1; 2048]); | |
| assert_eq!(&mmap[2048..3072], &[0; 1024]); | |
| assert_eq!(&mmap[3072..4096], &[2; 1024]); | |
| assert_eq!(&mmap[4096..8192], &[3; 4096]); | |
| } | |
| #[test] | |
| fn test_resize_shrink() { | |
| let file = tempfile::NamedTempFile::new().unwrap(); | |
| let mut mmap = Mmap::new(file.reopen().unwrap(), NonZeroUsize::new(8192).unwrap()).unwrap(); | |
| assert_eq!(mmap.len(), 8192); | |
| assert_eq!(file.as_file().metadata().unwrap().len(), 8192); | |
| mmap[0..8192].copy_from_slice(&[1; 8192]); | |
| mmap.resize(NonZeroUsize::new(4096).unwrap()).unwrap(); | |
| assert_eq!(mmap.len(), 4096); | |
| assert_eq!(file.as_file().metadata().unwrap().len(), 4096); | |
| assert_eq!(&mmap[0..4096], &[1; 4096]); | |
| mmap[2048..4096].copy_from_slice(&[2; 2048]); | |
| // munmap(2) succeeds. | |
| drop(mmap); | |
| let mmap = Mmap::new(file.reopen().unwrap(), NonZeroUsize::new(4096).unwrap()).unwrap(); | |
| assert_eq!(mmap.len(), 4096); | |
| assert_eq!(file.as_file().metadata().unwrap().len(), 4096); | |
| assert_eq!(&mmap[0..2048], &[1; 2048]); | |
| assert_eq!(&mmap[2048..4096], &[2; 2048]); | |
| } | |
| } |