use std::ffi::CString;
use std::io;
use std::ptr;
use crate::backend::Storage;
use crate::error::{Error, Result};
pub struct ShmStorage {
ptr: *mut u8,
size: usize,
owns: bool,
name: CString,
}
unsafe impl Send for ShmStorage {}
impl ShmStorage {
fn normalize_name(name: &str) -> Result<CString> {
let full = if let Some(stripped) = name.strip_prefix('/') {
format!("/{stripped}")
} else {
format!("/{name}")
};
CString::new(full).map_err(|e| Error::Io(io::Error::new(io::ErrorKind::InvalidInput, e)))
}
fn map(fd: i32, size: usize) -> io::Result<*mut u8> {
unsafe {
let ptr = libc::mmap(
ptr::null_mut(),
size,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_SHARED,
fd,
0,
);
if ptr == libc::MAP_FAILED {
return Err(io::Error::last_os_error());
}
Ok(ptr as *mut u8)
}
}
pub(crate) fn create(name: &str, size: u64) -> Result<Self> {
let cname = Self::normalize_name(name)?;
let size = size as usize;
unsafe {
let fd = libc::shm_open(
cname.as_ptr(),
libc::O_CREAT | libc::O_EXCL | libc::O_RDWR,
0o600,
);
if fd < 0 {
return Err(Error::Io(io::Error::last_os_error()));
}
if libc::ftruncate(fd, size as libc::off_t) != 0 {
let err = io::Error::last_os_error();
libc::close(fd);
libc::shm_unlink(cname.as_ptr());
return Err(Error::Io(err));
}
let ptr = match Self::map(fd, size) {
Ok(p) => p,
Err(e) => {
libc::close(fd);
libc::shm_unlink(cname.as_ptr());
return Err(Error::Io(e));
}
};
libc::close(fd); Ok(ShmStorage {
ptr,
size,
owns: true,
name: cname,
})
}
}
pub(crate) fn open(name: &str, size: u64) -> Result<Self> {
let cname = Self::normalize_name(name)?;
let size = size as usize;
unsafe {
let fd = libc::shm_open(cname.as_ptr(), libc::O_RDWR, 0o600);
if fd < 0 {
return Err(Error::Io(io::Error::last_os_error()));
}
let ptr = match Self::map(fd, size) {
Ok(p) => p,
Err(e) => {
libc::close(fd);
return Err(Error::Io(e));
}
};
libc::close(fd);
Ok(ShmStorage {
ptr,
size,
owns: false,
name: cname,
})
}
}
}
impl Storage for ShmStorage {
fn read_at(&self, buf: &mut [u8], offset: u64) -> Result<()> {
let offset = offset as usize;
let in_range = offset
.checked_add(buf.len())
.is_some_and(|end| end <= self.size);
if !in_range {
return Err(Error::Io(io::Error::new(
io::ErrorKind::UnexpectedEof,
"read_at out of range",
)));
}
unsafe {
ptr::copy_nonoverlapping(self.ptr.add(offset), buf.as_mut_ptr(), buf.len());
}
Ok(())
}
fn write_at(&self, buf: &[u8], offset: u64) -> Result<()> {
let offset = offset as usize;
let in_range = offset
.checked_add(buf.len())
.is_some_and(|end| end <= self.size);
if !in_range {
return Err(Error::Io(io::Error::new(
io::ErrorKind::WriteZero,
"write_at out of range",
)));
}
unsafe {
ptr::copy_nonoverlapping(buf.as_ptr(), self.ptr.add(offset), buf.len());
}
Ok(())
}
fn size(&self) -> u64 {
self.size as u64
}
fn close(self) -> Result<()> {
Ok(())
}
}
impl Drop for ShmStorage {
fn drop(&mut self) {
unsafe {
libc::munmap(self.ptr as *mut libc::c_void, self.size);
if self.owns {
libc::shm_unlink(self.name.as_ptr());
}
}
}
}