use super::error::{IpcError, last_os_error};
use core::slice;
#[cfg(unix)]
use super::backing_store::reserve_backing_store;
#[cfg(unix)]
use std::os::unix::io::RawFd;
#[cfg(windows)]
mod win {
pub const PAGE_READWRITE: u32 = 0x04;
pub const FILE_MAP_ALL_ACCESS: u32 = 0x000F_001F;
pub const INVALID_HANDLE_VALUE: usize = usize::MAX;
pub const ERROR_ALREADY_EXISTS: i32 = 183;
unsafe extern "system" {
pub fn CreateFileMappingW(
file: usize,
attributes: *mut core::ffi::c_void,
protect: u32,
size_high: u32,
size_low: u32,
name: *const u16,
) -> usize;
pub fn OpenFileMappingW(desired_access: u32, inherit: i32, name: *const u16) -> usize;
pub fn MapViewOfFile(
mapping: usize,
desired_access: u32,
offset_high: u32,
offset_low: u32,
size: usize,
) -> *mut core::ffi::c_void;
pub fn UnmapViewOfFile(address: *const core::ffi::c_void) -> i32;
pub fn CloseHandle(handle: usize) -> i32;
pub fn SetLastError(code: u32);
}
pub fn wide_name(name: &str) -> Vec<u16> {
name.trim_start_matches('/')
.encode_utf16()
.chain(core::iter::once(0))
.collect()
}
}
pub struct SharedMemory {
pub(crate) ptr: *mut u8,
pub(crate) size: usize,
#[cfg(unix)]
fd: RawFd,
#[cfg(windows)]
handle: usize,
#[cfg_attr(windows, allow(dead_code))]
owner: bool,
#[cfg(unix)]
name: Option<std::ffi::CString>,
}
#[cfg(unix)]
fn unix_mapping_length(size: usize) -> Result<libc::off_t, IpcError> {
if size == 0 {
return Err(IpcError::InvalidArgument);
}
libc::off_t::try_from(size).map_err(|_| IpcError::InvalidArgument)
}
unsafe impl Send for SharedMemory {}
unsafe impl Sync for SharedMemory {}
impl SharedMemory {
#[cfg(unix)]
pub fn create(name: &str, size: usize) -> Result<Self, IpcError> {
use std::ffi::CString;
let mapping_length = unix_mapping_length(size)?;
let c_name = CString::new(name).map_err(|_| IpcError::InvalidArgument)?;
unsafe {
use std::ptr::null_mut;
let fd = libc::shm_open(
c_name.as_ptr(),
libc::O_CREAT | libc::O_EXCL | libc::O_RDWR,
0o666,
);
if fd < 0 {
return Err(match last_os_error() {
IpcError::SystemError(libc::EEXIST) => IpcError::AlreadyExists,
other => other,
});
}
if libc::ftruncate(fd, mapping_length) < 0 {
libc::close(fd);
return Err(last_os_error());
}
if let Err(error) = reserve_backing_store(fd, mapping_length) {
libc::close(fd);
libc::shm_unlink(c_name.as_ptr());
return Err(error);
}
let ptr = libc::mmap(
null_mut(),
size,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_SHARED,
fd,
0,
);
if ptr == libc::MAP_FAILED {
libc::close(fd);
return Err(last_os_error());
}
Ok(Self {
ptr: ptr as *mut u8,
size,
fd,
owner: true,
name: Some(c_name),
})
}
}
#[cfg(unix)]
pub fn open(name: &str, size: usize) -> Result<Self, IpcError> {
use std::ffi::CString;
let mapping_length = unix_mapping_length(size)?;
let c_name = CString::new(name).map_err(|_| IpcError::InvalidArgument)?;
unsafe {
use std::ptr::null_mut;
let fd = libc::shm_open(c_name.as_ptr(), libc::O_RDWR, 0);
if fd < 0 {
return Err(last_os_error());
}
let mut segment = core::mem::MaybeUninit::<libc::stat>::uninit();
if libc::fstat(fd, segment.as_mut_ptr()) < 0 {
let error = last_os_error();
libc::close(fd);
return Err(error);
}
if segment.assume_init().st_size < mapping_length {
libc::close(fd);
return Err(IpcError::InvalidArgument);
}
let ptr = libc::mmap(
null_mut(),
size,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_SHARED,
fd,
0,
);
if ptr == libc::MAP_FAILED {
libc::close(fd);
return Err(last_os_error());
}
Ok(Self {
ptr: ptr as *mut u8,
size,
fd,
owner: false,
name: None,
})
}
}
#[cfg(windows)]
pub fn create(name: &str, size: usize) -> Result<Self, IpcError> {
if size == 0 {
return Err(IpcError::InvalidArgument);
}
let wide = win::wide_name(name);
#[allow(clippy::cast_possible_truncation)]
let size_low = size as u32;
let size_high = (size as u64 >> 32) as u32;
unsafe {
win::SetLastError(0);
let handle = win::CreateFileMappingW(
win::INVALID_HANDLE_VALUE,
core::ptr::null_mut(),
win::PAGE_READWRITE,
size_high,
size_low,
wide.as_ptr(),
);
if handle == 0 {
return Err(last_os_error());
}
if matches!(
last_os_error(),
IpcError::SystemError(win::ERROR_ALREADY_EXISTS)
) {
win::CloseHandle(handle);
return Err(IpcError::AlreadyExists);
}
let ptr = win::MapViewOfFile(handle, win::FILE_MAP_ALL_ACCESS, 0, 0, size);
if ptr.is_null() {
let error = last_os_error();
win::CloseHandle(handle);
return Err(error);
}
Ok(Self {
ptr: ptr as *mut u8,
size,
handle,
owner: true,
})
}
}
#[cfg(windows)]
pub fn open(name: &str, size: usize) -> Result<Self, IpcError> {
if size == 0 {
return Err(IpcError::InvalidArgument);
}
let wide = win::wide_name(name);
unsafe {
let handle = win::OpenFileMappingW(win::FILE_MAP_ALL_ACCESS, 0, wide.as_ptr());
if handle == 0 {
return Err(last_os_error());
}
let ptr = win::MapViewOfFile(handle, win::FILE_MAP_ALL_ACCESS, 0, 0, size);
if ptr.is_null() {
let error = last_os_error();
win::CloseHandle(handle);
return Err(error);
}
Ok(Self {
ptr: ptr as *mut u8,
size,
handle,
owner: false,
})
}
}
pub unsafe fn as_slice(&self) -> &[u8] {
unsafe { slice::from_raw_parts(self.ptr, self.size) }
}
pub unsafe fn as_mut_slice(&mut self) -> &mut [u8] {
unsafe { slice::from_raw_parts_mut(self.ptr, self.size) }
}
}
impl Drop for SharedMemory {
fn drop(&mut self) {
#[cfg(unix)]
unsafe {
libc::munmap(self.ptr as *mut libc::c_void, self.size);
libc::close(self.fd);
if self.owner
&& let Some(ref name) = self.name
{
libc::shm_unlink(name.as_ptr());
}
}
#[cfg(windows)]
unsafe {
win::UnmapViewOfFile(self.ptr as *const core::ffi::c_void);
win::CloseHandle(self.handle);
}
}
}