Skip to content

Commit e41e074

Browse files
wan9chicodex
andcommitted
feat(fspy-nostd): add Unix file mappings
Co-authored-by: GPT-5 Codex <codex@openai.com>
1 parent 8676c07 commit e41e074

3 files changed

Lines changed: 51 additions & 11 deletions

File tree

‎crates/fspy_nostd/src/mm.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@
88
//! pre-libc startup) and the crate-level backend check, which guarantees
99
//! they cannot silently turn into libc calls on Linux.
1010
11-
pub use rustix::mm::{MapFlags, MprotectFlags, ProtFlags, mmap_anonymous, mprotect, munmap};
11+
pub use rustix::mm::{MapFlags, MprotectFlags, ProtFlags, mmap, mmap_anonymous, mprotect, munmap};

‎crates/fspy_shm/Cargo.toml‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ publish = false
88
rust-version.workspace = true
99

1010
[dependencies]
11-
memmap2 = { workspace = true }
1211
uuid = { workspace = true, features = ["v4"] }
1312

1413
[target.'cfg(unix)'.dependencies]
1514
fspy_nostd = { workspace = true }
1615

16+
[target.'cfg(windows)'.dependencies]
17+
memmap2 = { workspace = true }
18+
1719
[target.'cfg(target_os = "windows")'.dependencies]
1820
windows-sys = { workspace = true, features = [
1921
"Win32_Foundation",

‎crates/fspy_shm/src/unix.rs‎

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ use std::{
99
num::NonZeroUsize,
1010
os::unix::{ffi::OsStrExt as _, fs::OpenOptionsExt as _, io::IntoRawFd as _},
1111
path::PathBuf,
12+
ptr::{self, NonNull},
1213
};
1314

14-
use memmap2::{MmapOptions, MmapRaw};
1515
use uuid::Uuid;
1616

1717
use crate::BACKING_PREFIX;
@@ -39,9 +39,17 @@ pub struct ShmHandle {
3939
/// A `Mapping` keeps the bytes alive until it is dropped and cannot affect the
4040
/// shared memory's identifier.
4141
pub struct Mapping {
42-
raw: MmapRaw,
42+
ptr: NonNull<u8>,
43+
len: NonZeroUsize,
4344
}
4445

46+
// SAFETY: a mapping owns no thread-affine state; access synchronization is
47+
// supplied by the fspy channel built on top of it.
48+
unsafe impl Send for Mapping {}
49+
// SAFETY: sharing a `Mapping` does not itself access its bytes, and all actual
50+
// concurrent access is synchronized by the fspy channel.
51+
unsafe impl Sync for Mapping {}
52+
4553
/// Creates `size` bytes of zero-initialized shared memory.
4654
///
4755
/// Returns its [`ShmKeeper`] and an already opened [`ShmHandle`], so the
@@ -171,23 +179,53 @@ impl ShmHandle {
171179
///
172180
/// Returns an error if the mapping cannot be established.
173181
pub fn map(&self) -> io::Result<Mapping> {
174-
let file = fspy_nostd::AsRawFd::as_raw_fd(&self.file);
175-
Ok(Mapping { raw: MmapOptions::new().len(self.size.get()).map_raw(file)? })
182+
let _slice_len = isize::try_from(self.size.get()).map_err(|_| {
183+
io::Error::new(io::ErrorKind::InvalidData, "shared-memory size exceeds isize")
184+
})?;
185+
// SAFETY: the address is only a hint, the validated nonzero length is
186+
// representable as a Rust slice, the descriptor remains borrowed, and
187+
// the resulting shared mapping is owned by `Mapping`.
188+
let mapped = unsafe {
189+
fspy_nostd::mm::mmap(
190+
ptr::null_mut(),
191+
self.size.get(),
192+
fspy_nostd::mm::ProtFlags::READ | fspy_nostd::mm::ProtFlags::WRITE,
193+
fspy_nostd::mm::MapFlags::SHARED,
194+
&self.file,
195+
0,
196+
)
197+
}
198+
.map_err(error_to_io)?;
199+
let Some(ptr) = NonNull::new(mapped.cast()) else {
200+
// Rust references cannot represent a mapping at address zero.
201+
// SAFETY: release the successful mapping before rejecting it.
202+
let _ = unsafe { fspy_nostd::mm::munmap(mapped, self.size.get()) };
203+
return Err(io::Error::other("mmap returned address zero"));
204+
};
205+
Ok(Mapping { ptr, len: self.size })
206+
}
207+
}
208+
209+
impl Drop for Mapping {
210+
fn drop(&mut self) {
211+
// SAFETY: this is the complete mapping owned by `self`, and dropping
212+
// it proves that no safe borrow through `self` remains.
213+
let _ = unsafe { fspy_nostd::mm::munmap(self.ptr.as_ptr().cast(), self.len.get()) };
176214
}
177215
}
178216

179217
#[expect(clippy::len_without_is_empty, reason = "shared-memory mappings are always non-empty")]
180218
impl Mapping {
181219
/// Returns the mapped length in bytes.
182220
#[must_use]
183-
pub fn len(&self) -> usize {
184-
self.raw.len()
221+
pub const fn len(&self) -> usize {
222+
self.len.get()
185223
}
186224

187225
/// Returns a raw pointer to the first mapped byte.
188226
#[must_use]
189-
pub fn as_ptr(&self) -> *mut u8 {
190-
self.raw.as_mut_ptr()
227+
pub const fn as_ptr(&self) -> *mut u8 {
228+
self.ptr.as_ptr()
191229
}
192230

193231
/// Returns the mapped bytes as a shared slice.
@@ -197,7 +235,7 @@ impl Mapping {
197235
/// The caller must ensure that no process or thread mutates the mapping for
198236
/// the lifetime of the returned slice.
199237
#[must_use]
200-
pub unsafe fn as_slice(&self) -> &[u8] {
238+
pub const unsafe fn as_slice(&self) -> &[u8] {
201239
// SAFETY: The mapping is valid for its full length, and the caller
202240
// guarantees that it is not mutated while the slice is borrowed.
203241
unsafe { std::slice::from_raw_parts(self.as_ptr().cast_const(), self.len()) }

0 commit comments

Comments
 (0)