use std::{fs::File, io, os::fd::AsRawFd, ptr::NonNull};
mod allocation;
pub struct SharedFileMapping {
address: NonNull<u8>,
length: usize,
}
unsafe impl Send for SharedFileMapping {}
impl SharedFileMapping {
pub fn new(file: &File, length: usize) -> io::Result<Self> {
let metadata = file.metadata()?;
if !metadata.is_file()
|| length == 0
|| length > isize::MAX as usize
|| length as u64 > metadata.len()
{
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"invalid mapped file extent",
));
}
require_local_filesystem(file)?;
allocation::reserve(file, length)?;
let address = unsafe {
libc::mmap(
std::ptr::null_mut(),
length,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_SHARED,
file.as_raw_fd(),
0,
)
};
if address == libc::MAP_FAILED {
return Err(io::Error::last_os_error());
}
let Some(address) = NonNull::new(address.cast::<u8>()) else {
unsafe { libc::munmap(address, length) };
return Err(io::Error::other("mapped file has a null address"));
};
Ok(Self { address, length })
}
pub fn mapped_len(&self) -> usize {
self.length
}
pub fn contains(&self, offset: u64, length: usize) -> bool {
self.start(offset, length).is_some()
}
fn start(&self, offset: u64, length: usize) -> Option<usize> {
let start = usize::try_from(offset).ok()?;
(start.checked_add(length)? <= self.length).then_some(start)
}
pub unsafe fn read(&self, offset: u64, bytes: &mut [u8]) -> bool {
let Some(start) = self.start(offset, bytes.len()) else {
return false;
};
unsafe {
std::ptr::copy_nonoverlapping(
self.address.as_ptr().add(start),
bytes.as_mut_ptr(),
bytes.len(),
);
}
true
}
pub unsafe fn write(&self, offset: u64, bytes: &[u8]) -> bool {
let Some(start) = self.start(offset, bytes.len()) else {
return false;
};
unsafe {
std::ptr::copy_nonoverlapping(
bytes.as_ptr(),
self.address.as_ptr().add(start),
bytes.len(),
);
}
true
}
pub unsafe fn write_published(
&self,
offset: u64,
bytes: &[u8],
state: usize,
pending: u8,
) -> bool {
use std::sync::atomic::{AtomicU8, Ordering};
let Some(start) = self
.start(offset, bytes.len())
.filter(|_| state < bytes.len())
else {
return false;
};
let publication = unsafe { AtomicU8::from_ptr(self.address.as_ptr().add(start + state)) };
publication.store(pending, Ordering::SeqCst);
unsafe {
self.write(offset, &bytes[..state]);
self.write(offset + state as u64 + 1, &bytes[state + 1..]);
}
#[cfg(test)]
assert!(
!INTERRUPT_PUBLICATION.replace(false),
"interrupted record publication"
);
publication.store(bytes[state], Ordering::Release);
true
}
}
#[cfg(test)]
thread_local! {
static INTERRUPT_PUBLICATION: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
}
impl Drop for SharedFileMapping {
fn drop(&mut self) {
unsafe { libc::munmap(self.address.as_ptr().cast(), self.length) };
}
}
fn require_local_filesystem(file: &File) -> io::Result<()> {
let mut filesystem = std::mem::MaybeUninit::<libc::statfs>::uninit();
if unsafe { libc::fstatfs(file.as_raw_fd(), filesystem.as_mut_ptr()) } != 0 {
return Err(io::Error::last_os_error());
}
let filesystem = unsafe { filesystem.assume_init() };
#[cfg(target_os = "linux")]
let supported = matches!(
filesystem.f_type as u64,
0xef53 | 0x5846_5342 | 0x9123_683e | 0x0102_1994 | 0x8584_58f6
);
#[cfg(target_os = "macos")]
let supported = {
let name = unsafe { std::ffi::CStr::from_ptr(filesystem.f_fstypename.as_ptr()) };
filesystem.f_flags & libc::MNT_LOCAL as u32 != 0
&& matches!(name.to_bytes(), b"apfs" | b"hfs")
};
if supported {
Ok(())
} else {
Err(io::Error::new(
io::ErrorKind::Unsupported,
"file mapping requires a supported local filesystem",
))
}
}
#[cfg(test)]
mod tests;