use super::err;
use crate::{error::FrozenResult, hints};
use core::{ffi::CStr, ptr};
use libc::{
EACCES, EAGAIN, EBADF, EBUSY, EINTR, EINVAL, EIO, ENODEV, ENOMEM, EOVERFLOW, EPERM, ETXTBSY, MAP_FAILED,
MAP_SHARED, MS_SYNC, PROT_READ, PROT_WRITE, c_void, mmap, msync, munmap, off_t, size_t, strerror,
};
type TPtr = *mut u8;
const MAX_RETRIES: usize = 0x0A;
#[derive(Debug)]
pub(super) struct POSIXMMap(TPtr);
unsafe impl Send for POSIXMMap {}
unsafe impl Sync for POSIXMMap {}
impl POSIXMMap {
pub(super) unsafe fn new(fd: i32, length: size_t) -> FrozenResult<Self> {
let ptr = mmap_raw(fd, length)?;
Ok(Self(ptr))
}
pub(super) unsafe fn unmap(&self, length: usize) -> FrozenResult<()> {
munmap_raw(self.0, length)
}
pub(super) unsafe fn sync(&self, length: usize) -> FrozenResult<()> {
msync_raw(self.0, length)
}
#[inline]
pub(super) unsafe fn as_mut_ptr<T>(&self, offset: usize) -> *mut T
where
T: Sized,
{
self.0.add(offset) as *mut T
}
#[inline]
pub(super) unsafe fn as_ptr<T>(&self, offset: usize) -> *const T
where
T: Sized,
{
self.0.add(offset) as *const T
}
}
unsafe fn mmap_raw(fd: i32, length: size_t) -> FrozenResult<TPtr> {
let mut retries = 0; loop {
let ptr = mmap(ptr::null_mut(), length, PROT_WRITE | PROT_READ, MAP_SHARED, fd, 0 as off_t);
if ptr == MAP_FAILED {
let errno = last_errno();
let err_msg = err_msg(errno);
match errno {
EINTR | EBUSY | EAGAIN => {
if retries < MAX_RETRIES {
retries += 1;
continue;
}
return err::new_err(err::UNK, err_msg);
}
EINVAL | EBADF | EOVERFLOW => return err::new_err(err::HCF, err_msg),
ENOMEM => return err::new_err(err::NMM, err_msg),
EACCES | EPERM | ENODEV | ETXTBSY => return err::new_err(err::PRM, err_msg),
_ => return err::new_err(err::UNK, err_msg),
};
}
return Ok(ptr as *mut u8);
}
}
unsafe fn munmap_raw(ptr: TPtr, length: size_t) -> FrozenResult<()> {
if munmap(ptr as *mut c_void, length) == 0 {
return Ok(());
}
let errno = last_errno();
let err_msg = err_msg(errno);
match errno {
EINVAL | ENOMEM => err::new_err(err::HCF, err_msg),
_ => err::new_err(err::UNK, err_msg),
}
}
unsafe fn msync_raw(ptr: TPtr, length: size_t) -> FrozenResult<()> {
let mut retries = 0; loop {
let res = msync(ptr as *mut c_void, length, MS_SYNC);
if hints::likely(res == 0) {
return Ok(());
}
let errno = last_errno();
let err_msg = err_msg(errno);
match errno {
EINTR | EBUSY | EAGAIN => {
if retries < MAX_RETRIES {
retries += 1;
continue;
}
return err::new_err(err::SYN, err_msg);
}
EIO => return err::new_err(err::SYN, err_msg),
EINVAL => return err::new_err(err::HCF, err_msg),
ENOMEM => return err::new_err(err::NMM, err_msg),
_ => return err::new_err(err::UNK, err_msg),
}
}
}
#[inline]
fn last_errno() -> i32 {
#[cfg(target_os = "linux")]
unsafe {
*libc::__errno_location()
}
#[cfg(target_os = "macos")]
unsafe {
*libc::__error()
}
}
#[inline]
unsafe fn err_msg(errno: i32) -> String {
let ptr = strerror(errno);
if ptr.is_null() {
return String::new();
}
CStr::from_ptr(ptr).to_string_lossy().into_owned()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ffile::{FrozenFile, FrozenFileCfg};
const MOD_ID: u8 = 0;
const BUFFER_SIZE: usize = 0x10;
const INIT_BUFFERS: usize = 0x0A;
const LENGTH: usize = BUFFER_SIZE * INIT_BUFFERS;
fn new_tmp() -> (tempfile::TempDir, FrozenFile) {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("tmp_map");
let file = FrozenFile::new(FrozenFileCfg {
path,
module_id: MOD_ID,
buffer_size: BUFFER_SIZE,
initial_available_buffers: INIT_BUFFERS,
})
.expect("new FF");
(dir, file)
}
mod utils {
use super::*;
#[test]
fn ok_last_errno() {
unsafe {
let _ = libc::close(-1);
assert_eq!(last_errno(), libc::EBADF);
}
}
#[test]
fn ok_err_msg() {
unsafe {
let msg = err_msg(libc::ENOENT);
assert!(!msg.is_empty(), "ENOENT must produce message");
}
}
}
mod map_unmap {
use super::*;
#[test]
fn ok_map_unmap_cycle() {
let (_dir, file) = new_tmp();
unsafe {
let mmap = POSIXMMap::new(file.fd(), LENGTH).unwrap();
mmap.unmap(LENGTH).unwrap();
}
}
#[test]
fn ok_map_zero_bytes_on_new() {
let (_dir, file) = new_tmp();
const BUF: [u8; LENGTH] = [0; LENGTH];
unsafe {
let mmap = POSIXMMap::new(file.fd(), LENGTH).unwrap();
let ptr = mmap.as_ptr::<[u8; LENGTH]>(0);
assert_eq!(*ptr, BUF);
mmap.unmap(LENGTH).unwrap();
}
}
#[test]
fn err_map_on_invalid_length() {
let (_dir, file) = new_tmp();
unsafe { assert!(POSIXMMap::new(file.fd(), 0).is_err()) };
}
#[test]
fn err_map_on_invalid_fd() {
let (_dir, _) = new_tmp();
unsafe { assert!(POSIXMMap::new(-1, LENGTH).is_err()) };
}
#[test]
fn err_unmap_on_invalid_length() {
let (_dir, file) = new_tmp();
unsafe {
let mmap = POSIXMMap::new(file.fd(), LENGTH).unwrap();
assert!(mmap.unmap(0).is_err());
}
}
}
mod map_sync {
use super::*;
#[test]
fn ok_sync() {
let (_dir, file) = new_tmp();
unsafe {
let mmap = POSIXMMap::new(file.fd(), LENGTH).unwrap();
mmap.sync(LENGTH).unwrap();
mmap.unmap(LENGTH).unwrap();
}
}
#[test]
fn ok_sync_after_sync() {
let (_dir, file) = new_tmp();
unsafe {
let mmap = POSIXMMap::new(file.fd(), LENGTH).unwrap();
mmap.sync(LENGTH).unwrap();
mmap.sync(LENGTH).unwrap();
mmap.sync(LENGTH).unwrap();
mmap.sync(LENGTH).unwrap();
mmap.unmap(LENGTH).unwrap();
}
}
}
mod map_write_read {
use super::*;
#[test]
fn ok_write_read_cycle() {
const VAL: u64 = 0xDEADC0DE;
let (_dir, file) = new_tmp();
unsafe {
let mmap = POSIXMMap::new(file.fd(), LENGTH).unwrap();
let wptr = mmap.as_mut_ptr::<u64>(0);
*wptr = VAL;
let rptr = mmap.as_ptr::<u64>(0);
assert_eq!(*rptr, VAL);
mmap.unmap(LENGTH).unwrap();
}
}
#[test]
fn ok_write_read_with_offset() {
const VAL: u64 = 0xDEADC0DE;
let (_dir, file) = new_tmp();
unsafe {
let mmap = POSIXMMap::new(file.fd(), LENGTH).unwrap();
let wptr = mmap.as_mut_ptr::<u64>(8);
*wptr = VAL;
let rptr = mmap.as_ptr::<u64>(8);
assert_eq!(*rptr, VAL);
mmap.unmap(LENGTH).unwrap();
}
}
#[test]
fn ok_write_read_sync_cycle() {
const VAL: [u32; 0x0A] = [0xDEADC0DE; 0x0A];
let (_dir, file) = new_tmp();
unsafe {
let mmap = POSIXMMap::new(file.fd(), LENGTH).unwrap();
let wptr = mmap.as_mut_ptr::<[u32; 0x0A]>(0);
*wptr = VAL;
mmap.sync(LENGTH).unwrap();
let rptr = mmap.as_ptr::<[u32; 0x0A]>(0);
assert_eq!(*rptr, VAL);
mmap.unmap(LENGTH).unwrap();
}
}
#[test]
fn ok_read_zero_bytes() {
let (_dir, file) = new_tmp();
unsafe {
let mmap = POSIXMMap::new(file.fd(), LENGTH).unwrap();
let rptr = mmap.as_ptr::<u64>(0);
assert_eq!(*rptr, 0);
mmap.unmap(LENGTH).unwrap();
}
}
}
mod map_durability {
use super::*;
#[test]
fn ok_map_durability_after_unmap() {
const VAL: u64 = 0xCAFEBABEDEADC0DE;
let (_dir, file) = new_tmp();
unsafe {
let mmap = POSIXMMap::new(file.fd(), LENGTH).unwrap();
let ptr = mmap.as_mut_ptr::<u64>(0);
*ptr = VAL;
mmap.sync(LENGTH).unwrap();
mmap.unmap(LENGTH).unwrap();
}
unsafe {
let mmap = POSIXMMap::new(file.fd(), LENGTH).unwrap();
let ptr = mmap.as_ptr::<u64>(0);
assert_eq!(*ptr, VAL);
mmap.unmap(LENGTH).unwrap();
}
}
#[test]
fn ok_map_durability_after_unmap_and_close() {
const VAL: u64 = 0xDEADC0DEDEADC0DE;
let (dir, file) = new_tmp();
unsafe {
let mmap = POSIXMMap::new(file.fd(), LENGTH).unwrap();
let ptr = mmap.as_mut_ptr::<u64>(0);
*ptr = VAL;
mmap.sync(LENGTH).unwrap();
mmap.unmap(LENGTH).unwrap();
drop(file);
}
unsafe {
let path = dir.path().join("tmp_map");
let cfg = FrozenFileCfg {
path,
module_id: MOD_ID,
buffer_size: BUFFER_SIZE,
initial_available_buffers: INIT_BUFFERS,
};
let file = FrozenFile::new(cfg).expect("new FF");
let mmap = POSIXMMap::new(file.fd(), LENGTH).unwrap();
let ptr = mmap.as_ptr::<u64>(0);
assert_eq!(*ptr, VAL);
mmap.unmap(LENGTH).unwrap();
}
}
}
}