blob: 9921224763f52cfbfde445203f39545f2ffaa04e [file] [log] [blame] [edit]
// 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]);
}
}