moonpool_explorer/shared_mem.rs
1//! POSIX shared memory allocation for cross-process data sharing.
2//!
3//! Provides `mmap(MAP_SHARED | MAP_ANONYMOUS)` wrappers for allocating memory
4//! visible across `fork()` boundaries. This is the foundation for all
5//! cross-process state in the explorer.
6
7use std::io;
8
9/// Allocate shared memory visible across `fork()` boundaries.
10///
11/// Returns a pointer to `size` bytes of zeroed memory backed by
12/// `MAP_SHARED | MAP_ANONYMOUS`. The memory is readable and writable
13/// by both parent and child processes after `fork()`.
14///
15/// # Errors
16///
17/// Returns an error if `mmap` fails (e.g., insufficient memory).
18#[cfg(unix)]
19pub fn alloc_shared(size: usize) -> Result<*mut u8, io::Error> {
20 // Safety: FFI call to libc::mmap.
21 // - MAP_ANONYMOUS: no file descriptor required (fd = -1).
22 // - MAP_SHARED: memory visible across fork() boundaries.
23 // - Kernel guarantees: returned memory is zeroed and page-aligned.
24 // - We check for MAP_FAILED before returning the pointer.
25 // - The caller owns the returned pointer and must free it via free_shared().
26 let ptr = unsafe {
27 libc::mmap(
28 std::ptr::null_mut(),
29 size,
30 libc::PROT_READ | libc::PROT_WRITE,
31 libc::MAP_SHARED | libc::MAP_ANONYMOUS,
32 -1,
33 0,
34 )
35 };
36 if ptr == libc::MAP_FAILED {
37 return Err(io::Error::last_os_error());
38 }
39 Ok(ptr as *mut u8)
40}
41
42/// Free shared memory allocated by [`alloc_shared`].
43///
44/// # Safety
45///
46/// - `ptr` must have been returned by [`alloc_shared`] with the same `size`.
47/// - `ptr` must not have been previously freed (no double-free).
48/// - The pointer and any derived references become invalid after this call.
49/// - `size` must exactly match the value passed to `alloc_shared`.
50#[cfg(unix)]
51pub unsafe fn free_shared(ptr: *mut u8, size: usize) {
52 unsafe {
53 libc::munmap(ptr as *mut libc::c_void, size);
54 }
55}
56
57/// No-op on non-unix platforms.
58#[cfg(not(unix))]
59pub fn alloc_shared(size: usize) -> Result<*mut u8, io::Error> {
60 Err(io::Error::new(
61 io::ErrorKind::Unsupported,
62 "shared memory requires unix",
63 ))
64}
65
66/// No-op on non-unix platforms.
67#[cfg(not(unix))]
68pub unsafe fn free_shared(_ptr: *mut u8, _size: usize) {}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73
74 #[test]
75 fn test_alloc_write_read_free() {
76 let size = 4096;
77 let ptr = alloc_shared(size).expect("alloc_shared failed");
78 assert!(!ptr.is_null());
79
80 // Write and read back
81 unsafe {
82 *ptr = 42;
83 *ptr.add(size - 1) = 99;
84 assert_eq!(*ptr, 42);
85 assert_eq!(*ptr.add(size - 1), 99);
86 free_shared(ptr, size);
87 }
88 }
89
90 #[test]
91 fn test_zeroed_on_alloc() {
92 let size = 1024;
93 let ptr = alloc_shared(size).expect("alloc_shared failed");
94
95 // Kernel guarantees zeroed memory from mmap
96 unsafe {
97 for i in 0..size {
98 assert_eq!(*ptr.add(i), 0);
99 }
100 free_shared(ptr, size);
101 }
102 }
103}