lowlet 0.1.2

Low-latency IPC library using shared memory and lock-free structures
Documentation
use std::ffi::CString;
use std::ptr::NonNull;

use crate::err::{Error, Result};

pub struct Region {
    ptr: NonNull<u8>,
    len: usize,
    fd: i32,
    name: CString,
    owner: bool,
}

unsafe impl Send for Region {}
unsafe impl Sync for Region {}

impl Region {
    pub fn create(name: &str, size: usize) -> Result<Self> {
        Self::validate_params(name, size)?;
        let cname = CString::new(name).map_err(|_| Error::ShmCreate)?;

        unsafe {
            libc::shm_unlink(cname.as_ptr());

            let fd = libc::shm_open(
                cname.as_ptr(),
                libc::O_CREAT | libc::O_RDWR | libc::O_EXCL,
                0o600,
            );

            if fd < 0 {
                return Err(Error::ShmCreate);
            }

            Self::complete_init(fd, cname, size, true)
        }
    }

    pub fn open(name: &str, size: usize) -> Result<Self> {
        Self::validate_params(name, size)?;
        let cname = CString::new(name).map_err(|_| Error::ShmOpen)?;

        unsafe {
            let fd = libc::shm_open(cname.as_ptr(), libc::O_RDWR, 0);

            if fd < 0 {
                return Err(Error::ShmOpen);
            }

            Self::complete_init(fd, cname, size, false)
        }
    }

    fn validate_params(name: &str, size: usize) -> Result<()> {
        if name.is_empty() || name.len() > 255 {
            return Err(Error::ShmCreate);
        }

        if size < 4096 || !size.is_power_of_two() {
            return Err(Error::InvalidSize);
        }

        Ok(())
    }

    unsafe fn complete_init(fd: i32, name: CString, size: usize, owner: bool) -> Result<Self> {
        if owner && libc::ftruncate(fd, size.try_into().unwrap_or(i64::MAX)) < 0 {
            libc::close(fd);
            libc::shm_unlink(name.as_ptr());
            return Err(Error::ShmResize);
        }

        let ptr = libc::mmap(
            std::ptr::null_mut(),
            size,
            libc::PROT_READ | libc::PROT_WRITE,
            libc::MAP_SHARED | libc::MAP_POPULATE,
            fd,
            0,
        );

        if ptr == libc::MAP_FAILED {
            libc::close(fd);
            if owner {
                libc::shm_unlink(name.as_ptr());
            }
            return Err(Error::Mmap);
        }

        libc::mlock(ptr, size);

        Ok(Self {
            ptr: NonNull::new_unchecked(ptr.cast()),
            len: size,
            fd,
            name,
            owner,
        })
    }

    #[inline]
    pub fn as_ptr(&self) -> *mut u8 {
        self.ptr.as_ptr()
    }

    #[inline]
    pub const fn len(&self) -> usize {
        self.len
    }

    #[inline]
    pub const fn is_empty(&self) -> bool {
        self.len == 0
    }

    #[inline]
    pub const fn is_owner(&self) -> bool {
        self.owner
    }

    #[inline]
    pub unsafe fn cast<T>(&self) -> &T {
        &*self.ptr.as_ptr().cast()
    }

    #[inline]
    #[allow(clippy::mut_from_ref)]
    pub unsafe fn cast_mut<T>(&self) -> &mut T {
        &mut *self.ptr.as_ptr().cast()
    }
}

impl Drop for Region {
    fn drop(&mut self) {
        unsafe {
            libc::munlock(self.ptr.as_ptr().cast(), self.len);
            libc::munmap(self.ptr.as_ptr().cast(), self.len);
            libc::close(self.fd);

            if self.owner {
                libc::shm_unlink(self.name.as_ptr());
            }
        }
    }
}