use super::{new_err, FMMapErr, ObjectInterface};
use crate::{error::FrozenRes, hints};
use core::{ffi::CStr, ptr};
use libc::{
c_void, mmap, msync, munmap, off_t, size_t, strerror, EACCES, EBADF, EBUSY, EINTR, EINVAL, EIO, ENOMEM, EOVERFLOW,
MAP_FAILED, MAP_SHARED, MS_SYNC, PROT_READ, PROT_WRITE,
};
type TPtr = *mut c_void;
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) -> FrozenRes<Self> {
let ptr = mmap_raw(fd, length)?;
Ok(Self(ptr))
}
pub(super) unsafe fn unmap(&self, length: usize) -> FrozenRes<()> {
munmap_raw(self.0, length)
}
pub(super) unsafe fn sync(&self, length: usize) -> FrozenRes<()> {
msync_raw(self.0, length)
}
#[inline]
pub(super) unsafe fn as_ptr<T>(&self, offset: usize) -> *mut ObjectInterface<T>
where
T: Sized + Send + Sync,
{
self.0.add(offset) as *mut ObjectInterface<T>
}
}
unsafe fn mmap_raw(fd: i32, length: size_t) -> FrozenRes<TPtr> {
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 {
EINVAL | EBADF | EACCES | EOVERFLOW => return new_err(FMMapErr::Hcf, err_msg),
ENOMEM => return new_err(FMMapErr::Nmm, err_msg),
_ => return new_err(FMMapErr::Unk, err_msg),
};
}
Ok(ptr)
}
unsafe fn munmap_raw(ptr: TPtr, length: size_t) -> FrozenRes<()> {
if munmap(ptr, length) == 0 {
return Ok(());
}
let errno = last_errno();
let err_msg = err_msg(errno);
match errno {
EINVAL | ENOMEM => new_err(FMMapErr::Hcf, err_msg),
_ => new_err(FMMapErr::Unk, err_msg),
}
}
unsafe fn msync_raw(ptr: TPtr, length: size_t) -> FrozenRes<()> {
let mut retries = 0; loop {
let res = msync(ptr, length, MS_SYNC);
if hints::likely(res == 0) {
return Ok(());
}
let errno = last_errno();
let err_msg = err_msg(errno);
match errno {
EINTR | EBUSY => {
if retries < MAX_RETRIES {
retries += 1;
continue;
}
return new_err(FMMapErr::Syn, err_msg);
}
EIO => return new_err(FMMapErr::Syn, err_msg),
EINVAL => return new_err(FMMapErr::Hcf, err_msg),
ENOMEM => return new_err(FMMapErr::Nmm, err_msg),
_ => return new_err(FMMapErr::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) -> Vec<u8> {
let ptr = strerror(errno);
if ptr.is_null() {
return Vec::new();
}
CStr::from_ptr(ptr).to_bytes().to_vec()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
error::TEST_MID,
ffile::{FFCfg, FrozenFile},
};
const CHUNK: usize = 0x10;
const INIT_CHUNKS: usize = 0x0A;
const LENGTH: usize = CHUNK * INIT_CHUNKS;
fn new_tmp() -> (tempfile::TempDir, FrozenFile) {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("tmp_map");
let cfg = FFCfg {
path,
mid: TEST_MID,
chunk_size: CHUNK,
initial_chunk_amount: INIT_CHUNKS,
};
let file = FrozenFile::new(cfg).expect("new FF");
(dir, file)
}
mod map_umap {
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);
let oi = &*ptr;
assert_eq!(*oi.get(), BUF);
mmap.unmap(LENGTH).unwrap();
}
}
#[test]
fn err_map_on_invalid_length() {
let (_dir, file) = new_tmp();
unsafe {
let err = POSIXMMap::new(file.fd(), 0).unwrap_err();
assert!(err.compare(FMMapErr::Hcf as u16));
}
}
#[test]
fn err_map_on_invalid_fd() {
let (_dir, _) = new_tmp();
unsafe {
let err = POSIXMMap::new(-1, LENGTH).unwrap_err();
assert!(err.compare(FMMapErr::Hcf as u16));
}
}
#[test]
fn err_unmap_on_invalid_length() {
let (_dir, file) = new_tmp();
unsafe {
let mmap = POSIXMMap::new(file.fd(), LENGTH).unwrap();
let err = mmap.unmap(0).unwrap_err();
assert!(err.compare(FMMapErr::Hcf as u16));
}
}
}
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_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() {
let (_dir, file) = new_tmp();
const VAL: u32 = 0xDEADC0DE;
unsafe {
let mmap = POSIXMMap::new(file.fd(), LENGTH).unwrap();
let wptr = mmap.as_ptr::<u32>(0);
let oi = &*wptr;
*oi.get_mut() = VAL;
let rptr = mmap.as_ptr::<u32>(0);
let oi = &*rptr;
assert_eq!(*oi.get(), VAL);
mmap.unmap(LENGTH).unwrap();
}
}
#[test]
fn ok_write_read_with_offset() {
let (_dir, file) = new_tmp();
const VAL: u32 = 0xDEADC0DE;
unsafe {
let mmap = POSIXMMap::new(file.fd(), LENGTH).unwrap();
let wptr = mmap.as_ptr::<u32>(0x0C);
let oi = &*wptr;
*oi.get_mut() = VAL;
let rptr = mmap.as_ptr::<u32>(0x0C);
let oi = &*rptr;
assert_eq!(*oi.get(), VAL);
mmap.unmap(LENGTH).unwrap();
}
}
#[test]
fn ok_write_read_sync_cycle() {
let (_dir, file) = new_tmp();
const VAL: [u32; 0x0A] = [0xDEADC0DE; 0x0A];
unsafe {
let mmap = POSIXMMap::new(file.fd(), LENGTH).unwrap();
let wptr = mmap.as_ptr::<[u32; 0x0A]>(0);
let oi = &*wptr;
*oi.get_mut() = VAL;
mmap.sync(LENGTH).unwrap();
let rptr = mmap.as_ptr::<[u32; 0x0A]>(0);
let oi = &*rptr;
assert_eq!(*oi.get(), 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);
let oi = &*rptr;
assert_eq!(*oi.get(), 0);
mmap.unmap(LENGTH).unwrap();
}
}
}
mod map_durability {
use super::*;
#[test]
fn ok_map_durability_after_unmap() {
let (_dir, file) = new_tmp();
const VAL: u64 = 0xCAFEBABEDEADC0DE;
unsafe {
let mmap = POSIXMMap::new(file.fd(), LENGTH).unwrap();
let ptr = mmap.as_ptr::<u64>(0);
*(*ptr).get_mut() = 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).get(), VAL);
mmap.unmap(LENGTH).unwrap();
}
}
#[test]
fn ok_map_durability_after_unmap_and_close() {
let (dir, file) = new_tmp();
const VAL: u64 = 0xCAFEBABEDEADC0DE;
unsafe {
let mmap = POSIXMMap::new(file.fd(), LENGTH).unwrap();
let ptr = mmap.as_ptr::<u64>(0);
*(*ptr).get_mut() = VAL;
mmap.sync(LENGTH).unwrap();
mmap.unmap(LENGTH).unwrap();
drop(file);
}
unsafe {
let path = dir.path().join("tmp_map");
let cfg = FFCfg {
mid: TEST_MID,
path,
chunk_size: CHUNK,
initial_chunk_amount: INIT_CHUNKS,
};
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).get(), VAL);
mmap.unmap(LENGTH).unwrap();
}
}
}
}