diff --git a/crates/fspy_shared/src/ipc/channel/mod.rs b/crates/fspy_shared/src/ipc/channel/mod.rs index 72881db8f..c5d367951 100644 --- a/crates/fspy_shared/src/ipc/channel/mod.rs +++ b/crates/fspy_shared/src/ipc/channel/mod.rs @@ -31,7 +31,7 @@ pub fn channel(capacity: usize) -> io::Result<(ChannelConf, Receiver)> { let shm_path = temp_dir.join(format!("fspy_ipc_{id}.shm")); let handle = with_shm_path(shm_path.as_os_str(), |path| fspy_shm::create(path, capacity))?; - let mapping = match handle.map() { + let mapping = match shm_result(handle.map()) { Ok(mapping) => mapping, Err(error) => { let _ = with_shm_path(shm_path.as_os_str(), fspy_shm::remove); @@ -61,7 +61,7 @@ impl ChannelConf { lock_file.try_lock_shared()?; let handle = with_shm_path(self.shm_path.to_cow_os_str().as_ref(), fspy_shm::open)?; - let mapping = handle.map()?; + let mapping = shm_result(handle.map())?; // SAFETY: `mapping` is a freshly mapped shared memory region with valid // pointer and size. Exclusive write access is ensured by the shared // file lock held by this sender. @@ -70,6 +70,16 @@ impl ChannelConf { } } +#[cfg(unix)] +fn shm_result(result: fspy_shm::Result) -> io::Result { + result.map_err(|error| io::Error::from_raw_os_error(error.raw_os_error())) +} + +#[cfg(windows)] +const fn shm_result(result: fspy_shm::Result) -> io::Result { + result +} + pub struct Sender { writer: ShmWriter, lock_file_path: Box, @@ -156,7 +166,7 @@ impl Receiver { #[cfg(unix)] fn with_shm_path( path: &OsStr, - call: impl FnOnce(fspy_shm::Path<'_>) -> io::Result, + call: impl FnOnce(fspy_shm::Path<'_>) -> fspy_shm::Result, ) -> io::Result { let path = CString::new(path.as_bytes()).map_err(|_| { io::Error::new(io::ErrorKind::InvalidInput, "shared-memory path contains NUL") @@ -164,15 +174,15 @@ fn with_shm_path( // SAFETY: `CString` owns an immutable NUL-terminated string through // `call`. let path = unsafe { fspy_shm::Path::from_ptr(path.as_ptr()) }; - call(path) + shm_result(call(path)) } #[cfg(windows)] fn with_shm_path( path: &OsStr, - call: impl FnOnce(fspy_shm::Path<'_>) -> io::Result, + call: impl FnOnce(fspy_shm::Path<'_>) -> fspy_shm::Result, ) -> io::Result { - call(path) + shm_result(call(path)) } pub struct ReceiverLockGuard<'a> { diff --git a/crates/fspy_shm/README.md b/crates/fspy_shm/README.md index ebee96faf..f16c01aa5 100644 --- a/crates/fspy_shm/README.md +++ b/crates/fspy_shm/README.md @@ -28,7 +28,7 @@ One implementation serves every platform: a sparse file at the full path supplie Only written pages ever occupy memory or disk. The multi-gigabyte capacity fspy asks for therefore costs about as much as the data a run actually records. -Unix creates, sizes, opens, maps, and unlinks through `sigsafe`; on Linux, its raw-syscall backend works before libc initialization. Windows maps through `memmap2`. The remaining platform-specific parts are short passages: +Unix builds without `std` and creates, sizes, opens, maps, and unlinks through `sigsafe`; on Linux, its raw-syscall backend works before libc initialization. Windows maps through `memmap2`. The remaining platform-specific parts are short passages: | Concern | Unix | Windows | | ----------------- | ---------------------------------------- | --------------------------------------------------------------------------- | diff --git a/crates/fspy_shm/src/lib.rs b/crates/fspy_shm/src/lib.rs index 25e7c3c29..0d510deeb 100644 --- a/crates/fspy_shm/src/lib.rs +++ b/crates/fspy_shm/src/lib.rs @@ -1,3 +1,4 @@ +#![cfg_attr(all(unix, not(test)), no_std)] #![doc = include_str!("../README.md")] #[cfg(unix)] @@ -5,7 +6,7 @@ mod unix; #[cfg(windows)] mod windows; -pub use platform::{Mapping, Path, ShmHandle, create, open, remove}; +pub use platform::{Error, Mapping, Path, Result, ShmHandle, create, open, remove}; #[cfg(unix)] use unix as platform; #[cfg(windows)] @@ -18,7 +19,7 @@ mod tests { use std::{ env::temp_dir, ffi::{OsStr, OsString}, - fs, io, + fs, mem::align_of, path::Path, process::Command, @@ -235,9 +236,9 @@ mod tests { #[cfg(unix)] fn with_path( path: &OsStr, - call: impl FnOnce(super::Path<'_>) -> io::Result, - ) -> io::Result { - let path = CString::new(path.as_bytes()).map_err(|_| io::ErrorKind::InvalidInput)?; + call: impl FnOnce(super::Path<'_>) -> super::Result, + ) -> super::Result { + let path = CString::new(path.as_bytes()).map_err(|_| sigsafe::Errno::INVAL)?; // SAFETY: `CString` owns one NUL-terminated string for the duration of // `call`. let path = unsafe { super::Path::from_ptr(path.as_ptr()) }; @@ -247,8 +248,8 @@ mod tests { #[cfg(windows)] fn with_path( path: &OsStr, - call: impl FnOnce(super::Path<'_>) -> io::Result, - ) -> io::Result { + call: impl FnOnce(super::Path<'_>) -> super::Result, + ) -> super::Result { call(path) } diff --git a/crates/fspy_shm/src/unix.rs b/crates/fspy_shm/src/unix.rs index 2d5fdcd6b..f6c6972c0 100644 --- a/crates/fspy_shm/src/unix.rs +++ b/crates/fspy_shm/src/unix.rs @@ -1,13 +1,17 @@ //! Unix shared memory backed by a sparse file at a caller-provided path. -use std::{ - io, +use core::{ num::NonZeroUsize, ptr::{self, NonNull}, + slice, }; /// Borrowed path accepted by shared-memory operations on Unix. pub type Path<'a> = sigsafe::CStr<'a, sigsafe::Thin>; +/// Error returned by shared-memory operations on Unix. +pub type Error = sigsafe::Error; +/// Result returned by shared-memory operations on Unix. +pub type Result = sigsafe::Result; /// Opened shared memory that is not mapped yet. /// @@ -45,13 +49,9 @@ unsafe impl Sync for Mapping {} /// # Errors /// /// Returns an error if the shared memory cannot be created or sized. -pub fn create(path: Path<'_>, size: usize) -> io::Result { - let size = NonZeroUsize::new(size).ok_or_else(|| { - io::Error::new(io::ErrorKind::InvalidInput, "shared-memory size must be nonzero") - })?; - let size_u64 = u64::try_from(size.get()).map_err(|_| { - io::Error::new(io::ErrorKind::InvalidInput, "shared-memory size exceeds u64") - })?; +pub fn create(path: Path<'_>, size: usize) -> Result { + let size = NonZeroUsize::new(size).ok_or(sigsafe::Errno::INVAL)?; + let size_u64 = u64::try_from(size.get()).map_err(|_| sigsafe::Errno::OVERFLOW)?; let file = open_file( path, sigsafe::fs::OFlags::RDWR @@ -64,7 +64,7 @@ pub fn create(path: Path<'_>, size: usize) -> io::Result { if let Err(error) = sigsafe::fs::ftruncate(&file, size_u64) { drop(file); let _ = remove(path); - return Err(errno_to_io(error)); + return Err(error); } Ok(ShmHandle { file, size }) @@ -76,7 +76,7 @@ pub fn create(path: Path<'_>, size: usize) -> io::Result { /// /// Returns an error if the shared memory is unavailable, including after its /// backing-file name has been removed. -pub fn open(path: Path<'_>) -> io::Result { +pub fn open(path: Path<'_>) -> Result { let file = open_file( path, sigsafe::fs::OFlags::RDWR | sigsafe::fs::OFlags::CLOEXEC, @@ -85,9 +85,9 @@ pub fn open(path: Path<'_>) -> io::Result { // If another process shrinks the file before `map`, mapping fails. If it // resizes afterwards, nothing here touches the mapped pages. A concurrent // resize cannot make a mapping access invalid memory. - let size = usize::try_from(sigsafe::fs::fstat(&file).map_err(errno_to_io)?.st_size) - .map_err(|_| io::ErrorKind::InvalidData)?; - let size = NonZeroUsize::new(size).ok_or(io::ErrorKind::InvalidData)?; + let size = usize::try_from(sigsafe::fs::fstat(&file)?.st_size) + .map_err(|_| sigsafe::Errno::OVERFLOW)?; + let size = NonZeroUsize::new(size).ok_or(sigsafe::Errno::INVAL)?; Ok(ShmHandle { file, size }) } @@ -99,21 +99,16 @@ pub fn open(path: Path<'_>) -> io::Result { /// # Errors /// /// Returns an error if the backing-file name cannot be removed. -pub fn remove(path: Path<'_>) -> io::Result<()> { +pub fn remove(path: Path<'_>) -> Result<()> { sigsafe::fs::unlinkat(sigsafe::CWD, path.count(), sigsafe::fs::AtFlags::empty()) - .map_err(errno_to_io) } fn open_file( path: Path<'_>, flags: sigsafe::fs::OFlags, mode: sigsafe::fs::Mode, -) -> io::Result { - sigsafe::fs::openat(sigsafe::CWD, path, flags, mode).map_err(errno_to_io) -} - -fn errno_to_io(errno: sigsafe::Errno) -> io::Error { - io::Error::from_raw_os_error(errno.raw_os_error()) +) -> Result { + sigsafe::fs::openat(sigsafe::CWD, path, flags, mode) } impl ShmHandle { @@ -122,7 +117,7 @@ impl ShmHandle { /// # Errors /// /// Returns an error if the mapping cannot be established. - pub fn map(&self) -> io::Result { + pub fn map(&self) -> Result { // SAFETY: the address is only a hint, the nonzero length is the // validated backing-file size, the descriptor remains borrowed, // and the resulting shared mapping is owned by `Mapping`. @@ -135,14 +130,13 @@ impl ShmHandle { &self.file, 0, ) - } - .map_err(errno_to_io)?; + }?; let Some(ptr) = NonNull::new(mapped.cast()) else { // A non-fixed mapping should not be placed at address zero, // which Rust cannot represent as a non-null allocation. // SAFETY: release the successful mapping before rejecting it. let _ = unsafe { sigsafe::mm::munmap(mapped, self.size.get()) }; - return Err(io::ErrorKind::Other.into()); + return Err(sigsafe::Errno::INVAL); }; Ok(Mapping { ptr, len: self.size }) } @@ -180,6 +174,6 @@ impl Mapping { pub const unsafe fn as_slice(&self) -> &[u8] { // SAFETY: The mapping is valid for its full length, and the caller // guarantees that it is not mutated while the slice is borrowed. - unsafe { std::slice::from_raw_parts(self.as_ptr().cast_const(), self.len()) } + unsafe { slice::from_raw_parts(self.as_ptr().cast_const(), self.len()) } } } diff --git a/crates/fspy_shm/src/windows.rs b/crates/fspy_shm/src/windows.rs index 1884bf6fe..849589c15 100644 --- a/crates/fspy_shm/src/windows.rs +++ b/crates/fspy_shm/src/windows.rs @@ -22,6 +22,10 @@ use windows_sys::Win32::{ /// Borrowed path accepted by shared-memory operations on Windows. pub type Path<'a> = &'a OsStr; +/// Error returned by shared-memory operations on Windows. +pub type Error = io::Error; +/// Result returned by shared-memory operations on Windows. +pub type Result = io::Result; const SHARE_ALL: u32 = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; const TEMPORARY: u32 = FILE_ATTRIBUTE_TEMPORARY; @@ -58,7 +62,7 @@ pub struct Mapping { /// /// Returns an error if the shared memory cannot be created or sized, or the /// containing volume does not support sparse files. -pub fn create(path: Path<'_>, size: usize) -> io::Result { +pub fn create(path: Path<'_>, size: usize) -> Result { if size == 0 { return Err(io::Error::new( io::ErrorKind::InvalidInput, @@ -99,7 +103,7 @@ pub fn create(path: Path<'_>, size: usize) -> io::Result { /// /// Returns an error if the shared memory is unavailable, including after its /// backing-file name has been removed. -pub fn open(path: Path<'_>) -> io::Result { +pub fn open(path: Path<'_>) -> Result { // Rust handles are non-inheritable, and its default share mode permits // concurrent read, write and delete access. let file = OpenOptions::new().read(true).write(true).open(path)?; @@ -122,7 +126,7 @@ pub fn open(path: Path<'_>) -> io::Result { /// # Errors /// /// Returns an error if removal cannot be performed or scheduled. -pub fn remove(path: Path<'_>) -> io::Result<()> { +pub fn remove(path: Path<'_>) -> Result<()> { if fs::remove_file(path).is_ok() { return Ok(()); } @@ -144,7 +148,7 @@ impl ShmHandle { /// # Errors /// /// Returns an error if the mapping cannot be established. - pub fn map(&self) -> io::Result { + pub fn map(&self) -> Result { Ok(Mapping { raw: MmapOptions::new().len(self.size).map_raw(&self.file)? }) } } @@ -178,7 +182,7 @@ impl Mapping { } /// Marks `file` sparse so that setting its length reserves no clusters. -fn set_sparse(file: &File) -> io::Result<()> { +fn set_sparse(file: &File) -> Result<()> { let mut bytes_returned = 0; // SAFETY: `file` supplies a valid synchronous file handle. FSCTL_SET_SPARSE // requires no input or output buffers, and `bytes_returned` is writable for @@ -200,7 +204,7 @@ fn set_sparse(file: &File) -> io::Result<()> { /// Returns the backing file's logical size and allocated byte count. #[cfg(test)] -pub fn file_sizes(file: &File) -> io::Result<(u64, u64)> { +pub fn file_sizes(file: &File) -> Result<(u64, u64)> { let mut info = FILE_STANDARD_INFO::default(); let info_size = u32::try_from(std::mem::size_of::()) .map_err(|_| io::Error::other("file size information is too large"))?;