waybackend 0.10.1

A simple, low-level wayland client implementation
Documentation
//! Utilities to use shared memory with memory mapping.
//!
//! I have placed this in `waybackend` because it is something that many applications will probably
//! need, so it makes sense to have it here in one place. Note the following code was heavily
//! inspired by what you can find in the `smithay` project.

use rustix::{
    fd::{AsFd, OwnedFd},
    io::Errno,
};

#[cfg(target_os = "linux")]
#[inline]
pub fn create() -> rustix::io::Result<OwnedFd> {
    match memfd() {
        Ok(fd) => Ok(fd),
        // Not supported, use fallback.
        Err(Errno::NOSYS) => fallback_shm(),
        Err(err) => Err(err),
    }
}

#[cfg(not(target_os = "linux"))]
#[inline]
pub fn create() -> rustix::io::Result<OwnedFd> {
    fallback_shm()
}

#[inline]
fn fallback_shm() -> rustix::io::Result<OwnedFd> {
    use rustix::{
        path::DecInt,
        shm::OFlags,
        time::{ClockId, clock_gettime},
    };
    const PREFIX: &[u8] = b"waybackend-";

    let mut write_buf = PREFIX.to_vec();

    let flags = OFlags::CREATE | OFlags::EXCL | OFlags::RDWR;
    let mode = rustix::shm::Mode::RUSR | rustix::shm::Mode::WUSR;

    loop {
        let filename = {
            let time = clock_gettime(ClockId::Monotonic);
            write_buf.truncate(PREFIX.len());
            write_buf.extend_from_slice(DecInt::new(time.tv_sec).as_bytes());
            write_buf.extend_from_slice(DecInt::new(time.tv_nsec).as_bytes());
            write_buf.as_slice()
        };
        match rustix::shm::open(filename, flags, mode) {
            Ok(fd) => return rustix::shm::unlink(filename).map(|()| fd),
            Err(Errno::EXIST | Errno::INTR) => continue,
            Err(err) => return Err(err),
        }
    }
}

#[cfg(target_os = "linux")]
#[inline]
fn memfd() -> rustix::io::Result<OwnedFd> {
    use rustix::fs::MemfdFlags;
    use rustix::fs::SealFlags;

    const FLAGS: MemfdFlags = MemfdFlags::ALLOW_SEALING.union(MemfdFlags::CLOEXEC);
    const NAME: &core::ffi::CStr = c"waybackend";
    const SEALS: SealFlags = SealFlags::SHRINK.union(SealFlags::SEAL);

    let fd = rustix::io::retry_on_intr(|| rustix::fs::memfd_create(NAME, FLAGS))?;
    let _ = rustix::fs::fcntl_add_seals(&fd, SEALS);

    Ok(fd)
}

/// A memory mapped slice that's meant to be used in ARBG/XRGB buffers
///
/// Careful that this creates a `[u32]` slice, so if you want raw bytes you will have to do this
/// manually
pub struct MmappedSlice<'a>(pub &'a mut [u32]);

impl<'a> MmappedSlice<'a> {
    /// `fd` must have been created through the [`create`] function in this module
    #[inline]
    pub fn new<Fd: AsFd>(fd: &'a mut Fd, len: usize, offset: u64) -> rustix::io::Result<Self> {
        use rustix::mm::{MapFlags, ProtFlags};
        const PROT: ProtFlags = ProtFlags::READ.union(ProtFlags::WRITE);
        const FLAGS: MapFlags = MapFlags::SHARED;
        let data = unsafe {
            let mmap = rustix::mm::mmap(core::ptr::null_mut(), len, PROT, FLAGS, fd, offset)?;
            core::slice::from_raw_parts_mut(mmap.cast(), len / core::mem::size_of::<u32>())
        };
        Ok(MmappedSlice(data))
    }
}

impl Drop for MmappedSlice<'_> {
    fn drop(&mut self) {
        unsafe { rustix::mm::munmap(self.0.as_mut_ptr().cast(), core::mem::size_of_val(self.0)) }
            .unwrap()
    }
}