Skip to main content

mmap_io/
anonymous.rs

1//! Anonymous (file-less) memory mappings.
2//!
3//! [`AnonymousMmap`] is a process-local, RW memory region with no
4//! backing file. Useful for scratch buffers shared between threads,
5//! large temporary allocations that should bypass the heap, or as the
6//! kernel-side substrate for fd-passing IPC patterns (where the
7//! anonymous mapping's fd is shared with a child process; not exposed
8//! here at 1.0 because the fd-passing surface is platform-specific).
9//!
10//! For shared memory between cooperating processes on the same host,
11//! the cross-platform pattern is a file-backed mapping where both
12//! processes map the same path. See `examples/10_ipc_shared_state.rs`
13//! and the `T6` cross-process integration test.
14//!
15//! ## Differences from [`MemoryMappedFile`]
16//!
17//! - No file descriptor / handle; no `AsFd`/`AsRawFd`/`AsHandle` impls.
18//! - No `resize` (`memmap2` does not support resizing anonymous maps).
19//! - No `flush` (volatile memory; nothing to persist).
20//! - No `path` (there is no path).
21//!
22//! Everything else (read, write, slice access, atomic views if the
23//! `atomic` feature is enabled) works identically.
24//!
25//! [`MemoryMappedFile`]: crate::mmap::MemoryMappedFile
26
27use memmap2::MmapMut;
28use parking_lot::RwLock;
29
30use crate::errors::{MmapIoError, Result};
31use crate::mmap::{MappedSlice, MappedSliceMut};
32use crate::utils::slice_range;
33
34// Mirrors the same constants used in `mmap.rs`. Kept module-local so a
35// future refactor of one does not silently drift the other.
36const ERR_ZERO_SIZE: &str = "Size must be greater than zero";
37
38#[cfg(target_pointer_width = "64")]
39const MAX_MMAP_SIZE: u64 = 128 * (1 << 40); // 128 TB
40
41#[cfg(target_pointer_width = "32")]
42const MAX_MMAP_SIZE: u64 = 2 * (1 << 30); // 2 GB
43
44/// Process-local anonymous memory mapping (no backing file).
45///
46/// Created via [`AnonymousMmap::new`]. The mapping is RW; pages are
47/// zero-initialized by the kernel on first touch. Memory is released
48/// when the value is dropped.
49///
50/// # Examples
51///
52/// ```
53/// use mmap_io::AnonymousMmap;
54///
55/// let mmap = AnonymousMmap::new(4096)?;
56/// mmap.update_region(0, b"hello")?;
57/// let mut buf = [0u8; 5];
58/// mmap.read_into(0, &mut buf)?;
59/// assert_eq!(&buf, b"hello");
60/// # Ok::<(), mmap_io::MmapIoError>(())
61/// ```
62pub struct AnonymousMmap {
63    map: RwLock<MmapMut>,
64    len: u64,
65}
66
67impl AnonymousMmap {
68    /// Allocate an anonymous RW mapping of `size` bytes.
69    ///
70    /// Pages are zero-initialized on first touch (kernel guarantee on
71    /// every supported platform: Linux, macOS, Windows).
72    ///
73    /// # Errors
74    ///
75    /// - [`MmapIoError::ResizeFailed`] if `size` is zero or exceeds the
76    ///   platform maximum (128 TB on 64-bit, 2 GB on 32-bit).
77    /// - [`MmapIoError::Io`] if the kernel rejects the allocation
78    ///   (out of address space, out of memory, etc.).
79    pub fn new(size: u64) -> Result<Self> {
80        if size == 0 {
81            return Err(MmapIoError::ResizeFailed(ERR_ZERO_SIZE.into()));
82        }
83        if size > MAX_MMAP_SIZE {
84            return Err(MmapIoError::ResizeFailed(format!(
85                "Size {size} exceeds maximum safe limit of {MAX_MMAP_SIZE} bytes"
86            )));
87        }
88        let len_usize = usize::try_from(size)
89            .map_err(|_| MmapIoError::ResizeFailed(format!("Size {size} does not fit in usize")))?;
90        let mmap = MmapMut::map_anon(len_usize)?;
91        Ok(Self {
92            map: RwLock::new(mmap),
93            len: size,
94        })
95    }
96
97    /// Length of the mapping in bytes.
98    #[must_use]
99    pub fn len(&self) -> u64 {
100        self.len
101    }
102
103    /// Whether the mapping has zero length. Always `false` for a
104    /// successfully-constructed `AnonymousMmap` (the constructor
105    /// rejects zero size); provided for API symmetry with
106    /// `MemoryMappedFile::is_empty`.
107    #[must_use]
108    pub fn is_empty(&self) -> bool {
109        self.len == 0
110    }
111
112    /// Copy `buf.len()` bytes from the mapping starting at `offset`
113    /// into `buf`.
114    ///
115    /// # Errors
116    ///
117    /// Returns [`MmapIoError::OutOfBounds`] if the range exceeds the
118    /// mapping length.
119    pub fn read_into(&self, offset: u64, buf: &mut [u8]) -> Result<()> {
120        let (start, end) = slice_range(offset, buf.len() as u64, self.len)?;
121        let guard = self.map.read();
122        buf.copy_from_slice(&guard[start..end]);
123        Ok(())
124    }
125
126    /// Write `data.len()` bytes into the mapping starting at `offset`.
127    ///
128    /// # Errors
129    ///
130    /// Returns [`MmapIoError::OutOfBounds`] if the range exceeds the
131    /// mapping length.
132    pub fn update_region(&self, offset: u64, data: &[u8]) -> Result<()> {
133        let (start, end) = slice_range(offset, data.len() as u64, self.len)?;
134        let mut guard = self.map.write();
135        guard[start..end].copy_from_slice(data);
136        Ok(())
137    }
138
139    /// Borrow a read-only slice of the mapping.
140    ///
141    /// The returned [`MappedSlice`] holds a read lock for its lifetime;
142    /// concurrent reads are fine, but any concurrent `as_mut_slice` or
143    /// `update_region` blocks until the slice is dropped.
144    ///
145    /// # Errors
146    ///
147    /// Returns [`MmapIoError::OutOfBounds`] if the range exceeds the
148    /// mapping length.
149    pub fn as_slice(&self, offset: u64, len: u64) -> Result<MappedSlice<'_>> {
150        let (start, end) = slice_range(offset, len, self.len)?;
151        let guard = self.map.read();
152        Ok(MappedSlice::guarded(guard, start..end))
153    }
154
155    /// Borrow a mutable slice of the mapping.
156    ///
157    /// The returned [`MappedSliceMut`] holds an exclusive write lock for
158    /// its lifetime; any concurrent reader or writer blocks until the
159    /// slice is dropped.
160    ///
161    /// # Errors
162    ///
163    /// Returns [`MmapIoError::OutOfBounds`] if the range exceeds the
164    /// mapping length.
165    pub fn as_mut_slice(&self, offset: u64, len: u64) -> Result<MappedSliceMut<'_>> {
166        let (start, end) = slice_range(offset, len, self.len)?;
167        let guard = self.map.write();
168        Ok(MappedSliceMut::guarded(guard, start..end))
169    }
170
171    /// Raw pointer to the start of the mapping.
172    ///
173    /// # Safety
174    ///
175    /// The caller must not retain the pointer beyond the lifetime of
176    /// this `AnonymousMmap`. Reads through the pointer require no
177    /// active mutable borrow elsewhere; writes through the pointer
178    /// require no other active borrow at all. Use this only when
179    /// bridging to FFI or unsafe code that needs a raw byte pointer.
180    #[must_use]
181    pub unsafe fn as_ptr(&self) -> *const u8 {
182        // SAFETY of the function (not of this line): documented above.
183        // This expression itself only takes a read lock and reads the
184        // mapping's base address; the read guard is dropped on return,
185        // which is the entire point of marking the function unsafe.
186        let guard = self.map.read();
187        guard.as_ptr()
188    }
189
190    /// Raw mutable pointer to the start of the mapping.
191    ///
192    /// # Safety
193    ///
194    /// Same constraints as [`as_ptr`](Self::as_ptr), plus: the caller
195    /// is responsible for ensuring no aliasing mutable references
196    /// exist for any byte they write to via this pointer.
197    #[must_use]
198    pub unsafe fn as_mut_ptr(&self) -> *mut u8 {
199        // SAFETY: see function docs. Lock guard is released on return.
200        let mut guard = self.map.write();
201        guard.as_mut_ptr()
202    }
203}
204
205impl std::fmt::Debug for AnonymousMmap {
206    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
207        f.debug_struct("AnonymousMmap")
208            .field("len", &self.len)
209            .finish()
210    }
211}