#![deny(missing_docs)]
use std::ops::{Deref, DerefMut};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::{mem, ptr};
#[cfg(feature = "os")]
pub mod os;
#[cfg(not(feature = "os"))]
mod os;
mod error;
pub use self::error::{ConvertResult, Error, Input, Operation, Result};
mod map;
pub use self::map::{Map, MapMut, Options};
#[cfg(feature = "io")]
pub mod io;
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum Protect {
ReadOnly,
ReadWrite,
ReadCopy,
ReadExec,
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum Flush {
Sync,
Async,
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum Advise {
Normal,
Sequential,
Random,
WillNeed,
WillNotNeed,
}
pub enum Extent {
End,
Exact(usize),
Min(usize),
Max(usize),
}
impl From<usize> for Extent {
fn from(v: usize) -> Self {
Self::Exact(v)
}
}
pub fn page_size() -> usize {
let size = PAGE_SIZE.load(Ordering::Relaxed);
if size == 0 {
load_system_info().0 as usize
} else {
size
}
}
pub fn allocation_size() -> usize {
let size = ALLOC_SIZE.load(Ordering::Relaxed);
if size == 0 {
load_system_info().1 as usize
} else {
size
}
}
static PAGE_SIZE: AtomicUsize = AtomicUsize::new(0);
static ALLOC_SIZE: AtomicUsize = AtomicUsize::new(0);
#[inline]
fn load_system_info() -> (u32, u32) {
let (page, alloc) = self::os::system_info();
PAGE_SIZE.store(page as usize, Ordering::Relaxed);
ALLOC_SIZE.store(alloc as usize, Ordering::Relaxed);
(page, alloc)
}
#[derive(Copy, Clone)]
pub struct Size(usize);
impl Size {
#[inline]
pub fn page() -> Self {
unsafe { Self::with_size(page_size()) }
}
#[inline]
pub fn alloc() -> Self {
unsafe { Self::with_size(allocation_size()) }
}
#[inline]
pub unsafe fn with_size(size: usize) -> Self {
Size(size)
}
#[inline]
pub const fn round(&self, len: usize) -> usize {
self.truncate(len + self.0 - 1)
}
#[inline]
pub const fn truncate(&self, len: usize) -> usize {
len & !(self.0 - 1)
}
#[inline]
pub const fn offset(&self, len: usize) -> usize {
len & (self.0 - 1)
}
#[inline]
pub const fn size(&self, count: u32) -> usize {
(count as usize) << self.0.trailing_zeros()
}
#[inline]
pub const fn count(&self, len: usize) -> u32 {
(self.round(len) >> self.0.trailing_zeros()) as u32
}
#[inline]
pub unsafe fn bounds(&self, ptr: *mut u8, len: usize) -> (*mut u8, usize) {
let off = self.offset(ptr as usize);
(ptr.offset(-(off as isize)), self.round(len + off))
}
}
impl Default for Size {
fn default() -> Self {
Self::alloc()
}
}
pub trait Span: Deref<Target = [u8]> + Sized + sealed::Span {
fn len(&self) -> usize;
fn as_ptr(&self) -> *const u8;
#[inline]
fn is_empty(&self) -> bool {
self.len() == 0
}
#[inline]
fn read_volatile<T: sealed::Scalar>(&self, offset: usize) -> T {
assert_capacity::<T>(offset, self.len());
assert_alignment::<T>(offset, self.as_ptr());
unsafe { ptr::read_volatile(self.as_ptr().add(offset) as *const T) }
}
#[inline]
fn read_unaligned<T: sealed::Scalar>(&self, offset: usize) -> T {
assert_capacity::<T>(offset, self.len());
unsafe { ptr::read_unaligned(self.as_ptr().add(offset) as *const T) }
}
}
pub trait SpanMut: Span + DerefMut {
fn as_mut_ptr(&mut self) -> *mut u8;
#[inline]
fn write_volatile<T: sealed::Scalar>(&mut self, offset: usize, value: T) {
assert_capacity::<T>(offset, self.len());
assert_alignment::<T>(offset, self.as_ptr());
unsafe { ptr::write_volatile(self.as_mut_ptr().add(offset) as *mut T, value) }
}
#[inline]
fn write_unaligned<T: sealed::Scalar>(&mut self, offset: usize, value: T) {
assert_capacity::<T>(offset, self.len());
unsafe { ptr::write_unaligned(self.as_mut_ptr().add(offset) as *mut T, value) }
}
}
impl<'a> Span for &'a [u8] {
#[inline]
fn len(&self) -> usize {
<[u8]>::len(self)
}
#[inline]
fn as_ptr(&self) -> *const u8 {
<[u8]>::as_ptr(self)
}
}
impl<'a> Span for &'a mut [u8] {
#[inline]
fn len(&self) -> usize {
<[u8]>::len(self)
}
#[inline]
fn as_ptr(&self) -> *const u8 {
<[u8]>::as_ptr(self)
}
}
impl<'a> SpanMut for &'a mut [u8] {
#[inline]
fn as_mut_ptr(&mut self) -> *mut u8 {
<[u8]>::as_mut_ptr(self)
}
}
mod sealed {
pub trait Span {}
impl Span for super::Map {}
impl Span for super::MapMut {}
impl<'a> Span for &'a [u8] {}
impl<'a> Span for &'a mut [u8] {}
pub trait FromPtr {
unsafe fn from_ptr(ptr: *mut u8, len: usize) -> Self;
}
pub trait Scalar: Default {}
impl Scalar for u8 {}
impl Scalar for i8 {}
impl Scalar for u16 {}
impl Scalar for i16 {}
impl Scalar for u32 {}
impl Scalar for i32 {}
impl Scalar for u64 {}
impl Scalar for i64 {}
impl Scalar for u128 {}
impl Scalar for i128 {}
impl Scalar for usize {}
impl Scalar for isize {}
impl Scalar for f32 {}
impl Scalar for f64 {}
}
#[inline]
fn assert_alignment<T>(offset: usize, ptr: *const u8) {
if unsafe { ptr.add(offset) } as usize % mem::align_of::<T>() != 0 {
panic!(
"offset improperly aligned: the requirement is {} but the offset is +{}/-{}",
mem::align_of::<T>(),
ptr as usize % mem::align_of::<T>(),
mem::align_of::<T>() - (ptr as usize % mem::align_of::<T>()),
)
}
}
#[inline]
fn assert_capacity<T>(offset: usize, len: usize) {
if offset + mem::size_of::<T>() > len {
panic!(
"index out of bounds: the len is {} but the index is {}",
len,
offset + mem::size_of::<T>()
)
}
}
#[cfg(test)]
mod tests {
use std::fs;
use std::path::PathBuf;
use std::str::from_utf8;
use super::*;
#[test]
fn allocation_size() {
let sz = unsafe { Size::with_size(4096) };
assert_eq!(sz.round(0), 0);
assert_eq!(sz.round(1), 4096);
assert_eq!(sz.round(4095), 4096);
assert_eq!(sz.round(4096), 4096);
assert_eq!(sz.round(4097), 8192);
assert_eq!(sz.truncate(0), 0);
assert_eq!(sz.truncate(1), 0);
assert_eq!(sz.truncate(4095), 0);
assert_eq!(sz.truncate(4096), 4096);
assert_eq!(sz.truncate(4097), 4096);
assert_eq!(sz.size(0), 0);
assert_eq!(sz.size(1), 4096);
assert_eq!(sz.size(2), 8192);
assert_eq!(sz.count(0), 0);
assert_eq!(sz.count(1), 1);
assert_eq!(sz.count(4095), 1);
assert_eq!(sz.count(4096), 1);
assert_eq!(sz.count(4097), 2);
assert_eq!(sz.count(8192), 2);
assert_eq!(sz.offset(0), 0);
assert_eq!(sz.offset(1), 1);
assert_eq!(sz.offset(4095), 4095);
assert_eq!(sz.offset(4096), 0);
assert_eq!(sz.offset(4097), 1);
}
#[test]
fn alloc_min() -> Result<()> {
let sz = Size::alloc();
let mut map = MapMut::with_options().len(Extent::Min(100)).alloc()?;
assert_eq!(map.len(), sz.round(100));
assert_eq!(Ok("\0\0\0\0\0"), from_utf8(&map[..5]));
map[..5].clone_from_slice(b"hello");
assert_eq!(Ok("hello"), from_utf8(&map[..5]));
Ok(())
}
#[test]
fn alloc_exact() -> Result<()> {
let mut map = MapMut::with_options().len(5).alloc()?;
assert_eq!(map.len(), 5);
assert_eq!(Ok("\0\0\0\0\0"), from_utf8(&map[..]));
map[..5].clone_from_slice(b"hello");
assert_eq!(Ok("hello"), from_utf8(&map[..]));
Ok(())
}
#[test]
fn alloc_offset() -> Result<()> {
let off = Size::alloc().size(1) - 5;
let mut map = MapMut::with_options().offset(off).len(6).alloc()?;
unsafe { os::protect(map.as_mut_ptr().add(5), 1, Protect::ReadOnly)? };
assert_eq!(map.len(), 6);
assert_eq!(Ok("\0\0\0\0\0\0"), from_utf8(&map[..]));
map[..5].clone_from_slice(b"hello");
assert_eq!(Ok("hello\0"), from_utf8(&map[..]));
Ok(())
}
#[test]
fn read_end() -> Result<()> {
let (_tmp, path, len) = write_default("read_end")?;
let (map, _) = Map::with_options().offset(29).open(&path)?;
assert!(map.len() >= 30);
assert_eq!(len - 29, map.len());
assert_eq!(Ok("fast and safe memory-mapped IO"), from_utf8(&map[..30]));
Ok(())
}
#[test]
fn read_min() -> Result<()> {
let (_tmp, path, len) = write_default("read_min")?;
let (map, _) = Map::with_options()
.offset(29)
.len(Extent::Min(30))
.open(&path)?;
println!("path = {:?}, len = {}, map = {}", path, len, map.len());
assert!(map.len() >= 30);
assert_eq!(len - 29, map.len());
assert_eq!(Ok("fast and safe memory-mapped IO"), from_utf8(&map[..30]));
Ok(())
}
#[test]
fn read_max() -> Result<()> {
let (_tmp, path, _len) = write_default("read_max")?;
let (map, _) = Map::with_options()
.offset(29)
.len(Extent::Max(30))
.open(&path)?;
assert!(map.len() == 30);
assert_eq!(Ok("fast and safe memory-mapped IO"), from_utf8(&map[..]));
Ok(())
}
#[test]
fn read_exact() -> Result<()> {
let (_tmp, path, _len) = write_default("read_exact")?;
let (map, _) = Map::with_options().offset(29).len(30).open(&path)?;
assert!(map.len() == 30);
assert_eq!(Ok("fast and safe memory-mapped IO"), from_utf8(&map[..]));
Ok(())
}
#[test]
fn copy() -> Result<()> {
let (_tmp, path, _len) = write_default("copy")?;
let (mut map, _) = MapMut::with_options()
.offset(29)
.len(30)
.copy()
.open(&path)?;
assert_eq!(map.len(), 30);
assert_eq!(Ok("fast and safe memory-mapped IO"), from_utf8(&map[..]));
map[..4].clone_from_slice(b"nice");
assert_eq!(Ok("nice and safe memory-mapped IO"), from_utf8(&map[..]));
Ok(())
}
#[test]
fn write_into_mut() -> Result<()> {
let tmp = tempdir::TempDir::new("vmap")?;
let path: PathBuf = tmp.path().join("write_into_mut");
fs::write(&path, "this is a test").expect("failed to write file");
let (map, _) = Map::with_options().write().resize(16).open(&path)?;
assert_eq!(16, map.len());
assert_eq!(Ok("this is a test"), from_utf8(&map[..14]));
assert_eq!(Ok("this is a test\0\0"), from_utf8(&map[..]));
let mut map = map.into_map_mut()?;
map[..4].clone_from_slice(b"that");
assert_eq!(Ok("that is a test"), from_utf8(&map[..14]));
assert_eq!(Ok("that is a test\0\0"), from_utf8(&map[..]));
let map = map.into_map()?;
assert_eq!(Ok("that is a test"), from_utf8(&map[..14]));
assert_eq!(Ok("that is a test\0\0"), from_utf8(&map[..]));
let (map, _) = Map::with_options().open(&path)?;
assert_eq!(16, map.len());
assert_eq!(Ok("that is a test"), from_utf8(&map[..14]));
assert_eq!(Ok("that is a test\0\0"), from_utf8(&map[..]));
Ok(())
}
#[test]
fn truncate() -> Result<()> {
let tmp = tempdir::TempDir::new("vmap")?;
let path: PathBuf = tmp.path().join("truncate");
fs::write(&path, "this is a test").expect("failed to write file");
let (map, _) = Map::with_options()
.write()
.truncate(true)
.resize(16)
.open(&path)?;
assert_eq!(16, map.len());
assert_eq!(Ok("\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"), from_utf8(&map[..]));
Ok(())
}
type WriteResult = Result<(tempdir::TempDir, PathBuf, usize)>;
fn write_tmp(name: &'static str, msg: &'static str) -> WriteResult {
let tmp = tempdir::TempDir::new("vmap")?;
let path: PathBuf = tmp.path().join(name);
fs::write(&path, msg)?;
Ok((tmp, path, msg.len()))
}
fn write_default(name: &'static str) -> WriteResult {
write_tmp(
name,
"A cross-platform library for fast and safe memory-mapped IO in Rust",
)
}
#[test]
fn volatile() -> Result<()> {
let tmp = tempdir::TempDir::new("vmap")?;
let path: PathBuf = tmp.path().join("volatile");
let (mut map, _) = MapMut::with_options()
.write()
.truncate(true)
.create(true)
.resize(16)
.open(&path)?;
assert_eq!(16, map.len());
assert_eq!(0u64, map.read_volatile(0));
assert_eq!(0u64, map.read_volatile(8));
map.write_volatile(0, 0xc3a5c85c97cb3127u64);
map.write_volatile(8, 0xb492b66fbe98f273u64);
assert_eq!(0xc3a5c85c97cb3127u64, map.read_volatile(0));
assert_eq!(0xb492b66fbe98f273u64, map.read_volatile(8));
let (map, _) = Map::with_options().open(&path)?;
assert_eq!(16, map.len());
assert_eq!(0xc3a5c85c97cb3127u64, map.read_volatile(0));
assert_eq!(0xb492b66fbe98f273u64, map.read_volatile(8));
Ok(())
}
#[test]
fn unaligned() -> Result<()> {
let tmp = tempdir::TempDir::new("vmap")?;
let path: PathBuf = tmp.path().join("unaligned");
let (mut map, _) = MapMut::with_options()
.write()
.truncate(true)
.create(true)
.resize(17)
.open(&path)?;
assert_eq!(17, map.len());
assert_eq!(0u64, map.read_unaligned(1));
assert_eq!(0u64, map.read_unaligned(9));
map.write_unaligned(1, 0xc3a5c85c97cb3127u64);
map.write_unaligned(9, 0xb492b66fbe98f273u64);
assert_eq!(0xc3a5c85c97cb3127u64, map.read_unaligned(1));
assert_eq!(0xb492b66fbe98f273u64, map.read_unaligned(9));
let (map, _) = Map::with_options().open(&path)?;
assert_eq!(17, map.len());
assert_eq!(0xc3a5c85c97cb3127u64, map.read_unaligned(1));
assert_eq!(0xb492b66fbe98f273u64, map.read_unaligned(9));
Ok(())
}
}