From c311dd09a1e87aff13e1d7fd20b593231fea1f6e Mon Sep 17 00:00:00 2001 From: social4hyq Date: Sun, 7 Jun 2026 13:16:09 +0800 Subject: [PATCH 1/3] deps(shared_memory): vendor 0.12.4 with nix 0.30 patch for OHOS support shared_memory 0.12.4 pins nix = "0.23", which does not gate aio_*, lio_listio, FDPIC_FUNCPTRS, UNAME26, __fsword_t, ST_RELATIME etc. on target_env = "ohos" and thus fails to build for aarch64-unknown-linux-ohos. Vendor the crate at crates/vendor_shared_memory with publish = false, bump nix to 0.30 (default-features = false, features = ["fs", "mman"]), and adapt the API surface: shm_open returns OwnedFd (convert via into_raw_fd); ftruncate, fstat, and mmap take BorrowedFd; mmap takes Option for size and NonNull for addr; munmap takes NonNull. The vendor crate keeps the upstream package name shared_memory so fspy_shared consumes it unchanged through the workspace dependency. Workspace Cargo.toml swaps the entry from "0.12.4" to a path reference. Exit condition: delete this directory and revert to the crates.io dependency once upstream ships a release with nix >= 0.30 (tracked at https://github.com/elast0ny/shared_memory-rs). Same patch lives in HarmonyBrew tap at Patches/shared_memory@0.12.4/0001-ohos-nix-030.patch so both consumers stay in lockstep. Verified by cargo check -p shared_memory and cargo check -p fspy_shared on aarch64-unknown-linux-ohos (rustc 1.95 + RUSTC_BOOTSTRAP=1). --- Cargo.lock | 36 +-- Cargo.toml | 2 +- crates/vendor_shared_memory/Cargo.toml | 36 +++ crates/vendor_shared_memory/README.md | 47 ++++ crates/vendor_shared_memory/changelog.md | 20 ++ crates/vendor_shared_memory/src/error.rs | 48 ++++ crates/vendor_shared_memory/src/lib.rs | 278 ++++++++++++++++++++ crates/vendor_shared_memory/src/unix.rs | 225 +++++++++++++++++ crates/vendor_shared_memory/src/windows.rs | 281 +++++++++++++++++++++ 9 files changed, 948 insertions(+), 25 deletions(-) create mode 100644 crates/vendor_shared_memory/Cargo.toml create mode 100644 crates/vendor_shared_memory/README.md create mode 100644 crates/vendor_shared_memory/changelog.md create mode 100644 crates/vendor_shared_memory/src/error.rs create mode 100644 crates/vendor_shared_memory/src/lib.rs create mode 100644 crates/vendor_shared_memory/src/unix.rs create mode 100644 crates/vendor_shared_memory/src/windows.rs diff --git a/Cargo.lock b/Cargo.lock index 3da36c59..252d6384 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1977,15 +1977,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a64a92489e2744ce060c349162be1c5f33c6969234104dbd99ddb5feb08b8c15" -[[package]] -name = "memoffset" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" -dependencies = [ - "autocfg", -] - [[package]] name = "memoffset" version = "0.9.1" @@ -2056,40 +2047,39 @@ dependencies = [ [[package]] name = "nix" -version = "0.23.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" +checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ - "bitflags 1.3.2", - "cc", + "bitflags 2.10.0", "cfg-if", + "cfg_aliases 0.1.1", "libc", - "memoffset 0.6.5", ] [[package]] name = "nix" -version = "0.28.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ "bitflags 2.10.0", "cfg-if", - "cfg_aliases 0.1.1", + "cfg_aliases 0.2.1", "libc", + "memoffset", ] [[package]] name = "nix" -version = "0.29.0" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ "bitflags 2.10.0", "cfg-if", "cfg_aliases 0.2.1", "libc", - "memoffset 0.9.1", ] [[package]] @@ -2102,7 +2092,7 @@ dependencies = [ "cfg-if", "cfg_aliases 0.2.1", "libc", - "memoffset 0.9.1", + "memoffset", ] [[package]] @@ -3294,13 +3284,11 @@ dependencies = [ [[package]] name = "shared_memory" version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba8593196da75d9dc4f69349682bd4c2099f8cde114257d1ef7ef1b33d1aba54" dependencies = [ "cfg-if", "libc", "log", - "nix 0.23.2", + "nix 0.30.1", "rand 0.8.5", "win-sys", ] diff --git a/Cargo.toml b/Cargo.toml index a7339350..5756ef24 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -117,7 +117,7 @@ serde = "1.0.219" serde_json = "1.0.140" serde_norway = "0.9.42" sha2 = "0.11.0" -shared_memory = "0.12.4" +shared_memory = { path = "crates/vendor_shared_memory" } shell-escape = "0.1.5" similar = "3.0.0" smallvec = { version = "2.0.0-alpha.12", features = ["std"] } diff --git a/crates/vendor_shared_memory/Cargo.toml b/crates/vendor_shared_memory/Cargo.toml new file mode 100644 index 00000000..05c88c41 --- /dev/null +++ b/crates/vendor_shared_memory/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "shared_memory" +description = "A user friendly crate that allows you to share memory between processes" +version = "0.12.4" +authors = ["ElasT0ny "] +license = "MIT OR Apache-2.0" +edition = "2018" +publish = false + +readme = "README.md" +documentation = "https://docs.rs/shared_memory" +repository = "https://github.com/elast0ny/shared_memory-rs" +keywords = ["shmem", "shared", "memory", "inter-process", "process"] +categories = [ + "os::unix-apis", + "os::windows-apis", + "memory-management", + "concurrency", + "asynchronous", +] + +[features] +default = [] +logging = ["log"] + +[dependencies] +cfg-if = "1.0" +rand = "0.8" +log = { version = "0.4", optional = true } + +[target.'cfg(unix)'.dependencies] +nix = { version = "0.30", default-features = false, features = ["fs", "mman"] } +libc = "0.2" + +[target.'cfg(windows)'.dependencies] +win-sys = "0.3" diff --git a/crates/vendor_shared_memory/README.md b/crates/vendor_shared_memory/README.md new file mode 100644 index 00000000..bc19a770 --- /dev/null +++ b/crates/vendor_shared_memory/README.md @@ -0,0 +1,47 @@ +# vendor_shared_memory + +Vendored copy of [`shared_memory`](https://github.com/elast0ny/shared_memory-rs) v0.12.4 with a patch +that bumps `nix` from 0.23 to 0.30 so the crate compiles for `*-unknown-linux-ohos` targets. + +This crate keeps the upstream package name `shared_memory` so workspace consumers (`fspy_shared`) +import it unchanged. + +## Why vendor + +`shared_memory 0.12.4` pins `nix = "0.23"`. `nix 0.23.x` does not gate a handful of syscalls and +constants on `target_env = "ohos"` (`aio_*`, `lio_listio`, `FDPIC_FUNCPTRS`, `UNAME26`, +`__fsword_t`, `ST_RELATIME`, etc.), so it fails to build for `aarch64-unknown-linux-ohos`. + +`nix 0.30` adds the missing OHOS gates and reworks the fd-borrowing API. The patched source uses +the new API surface: `shm_open` returns `OwnedFd` (converted via `into_raw_fd`); `ftruncate`, +`fstat`, and `mmap` take `BorrowedFd`; `mmap` takes `Option` for size and `NonNull` +for `addr`; `munmap` takes `NonNull`. + +## Patch source + +The patch lives in HarmonyBrew tap at +[`Patches/shared_memory@0.12.4/0001-ohos-nix-030.patch`](https://github.com/Harmonybrew/homebrew-core/blob/main/Patches/shared_memory%400.12.4/0001-ohos-nix-030.patch). +Both this vendor crate and the brew formula consume the same patch so OHOS support stays in lockstep. + +## Exit condition + +Delete this crate as soon as upstream `shared_memory` ships a release with `nix` ≥ 0.30 +(tracked at ). When that happens: + +1. In the workspace `Cargo.toml`, change + `shared_memory = { path = "crates/vendor_shared_memory" }` back to + `shared_memory = ""`. +2. Remove this directory. +3. Drop the `Patches/shared_memory@0.12.4/` patch from the HarmonyBrew tap. + +## What was omitted + +The vendor crate keeps only `src/` (the library code) and the changelog. Upstream's `tests/` and +`examples/` directories — and their dev-dependencies (`raw_sync`, `clap 3`, `env_logger`) — are +left out because they're not required for `fspy_shared` to consume the library and they would pull +old transitive deps into the workspace. + +## License + +Upstream license: `MIT OR Apache-2.0` (see the `license` field in `Cargo.toml`). +Upstream copyright: ElasT0ny <elast0ny00@gmail.com>. diff --git a/crates/vendor_shared_memory/changelog.md b/crates/vendor_shared_memory/changelog.md new file mode 100644 index 00000000..ba19aa99 --- /dev/null +++ b/crates/vendor_shared_memory/changelog.md @@ -0,0 +1,20 @@ +# Changelog + +# 0.12.4 +- Allow Windows users to open shared memory that isnt managed by this crate +- Added tests + +# 0.12.2 +- Default feature behavior is to disable logging on release builds +- Reverted edition bump back to 2018 +- Updated to use Microsoft's `windows-rs` crate + +# ~~0.12.1 (yanked)~~ +- ~~Updated to latest edition (2021)~~ + +# 0.12.0 +- Windows implementation now follows POSIX behavior in regards to ownership and deletion, see [#59](https://github.com/elast0ny/shared_memory-rs/pull/59) for more details +# __0.11.X__ +This release breaks backwards compatibility and removes a bunch of previous features which hid many unsafe behaviors (automatically casting shared memory to Rust types). + +The release also marks the split between `shared_memory` and its synchronization primitives into a seperate crate `raw_sync`. \ No newline at end of file diff --git a/crates/vendor_shared_memory/src/error.rs b/crates/vendor_shared_memory/src/error.rs new file mode 100644 index 00000000..4f5014ca --- /dev/null +++ b/crates/vendor_shared_memory/src/error.rs @@ -0,0 +1,48 @@ +#[derive(Debug)] +pub enum ShmemError { + MapSizeZero, + NoLinkOrOsId, + FlinkInvalidOsId, + LinkCreateFailed(std::io::Error), + LinkWriteFailed(std::io::Error), + LinkExists, + LinkOpenFailed(std::io::Error), + LinkReadFailed(std::io::Error), + LinkDoesNotExist, + MappingIdExists, + MapCreateFailed(u32), + MapOpenFailed(u32), + UnknownOsError(u32), +} + +impl std::fmt::Display for ShmemError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ShmemError::MapSizeZero => f.write_str("You cannot create a shared memory mapping of 0 size"), + ShmemError::NoLinkOrOsId => f.write_str("Tried to open mapping without flink path or os_id"), + ShmemError::FlinkInvalidOsId => f.write_str("Tried to open mapping from both flink and os_id but the flink did not point to the same os_id"), + ShmemError::LinkCreateFailed(err) => write!(f, "Creating the link file failed, {}", err), + ShmemError::LinkWriteFailed(err) => write!(f, "Writing the link file failed, {}", err), + ShmemError::LinkExists => f.write_str("Shared memory link already exists"), + ShmemError::LinkOpenFailed(err) => write!(f, "Opening the link file failed, {}", err), + ShmemError::LinkReadFailed(err) => write!(f, "Reading the link file failed, {}", err), + ShmemError::LinkDoesNotExist => f.write_str("Requested link file does not exist"), + ShmemError::MappingIdExists => f.write_str("Shared memory OS specific ID already exists"), + ShmemError::MapCreateFailed(err) => write!(f, "Creating the shared memory failed, os error {}", err), + ShmemError::MapOpenFailed(err) => write!(f, "Opening the shared memory failed, os error {}", err), + ShmemError::UnknownOsError(err) => write!(f, "An unexpected OS error occurred, os error {}", err), + } + } +} + +impl std::error::Error for ShmemError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + ShmemError::LinkCreateFailed(err) => Some(err), + ShmemError::LinkWriteFailed(err) => Some(err), + ShmemError::LinkOpenFailed(err) => Some(err), + ShmemError::LinkReadFailed(err) => Some(err), + _ => None, + } + } +} diff --git a/crates/vendor_shared_memory/src/lib.rs b/crates/vendor_shared_memory/src/lib.rs new file mode 100644 index 00000000..3f059378 --- /dev/null +++ b/crates/vendor_shared_memory/src/lib.rs @@ -0,0 +1,278 @@ +//! A thin wrapper around shared memory system calls +//! +//! For help on how to get started, take a look at the [examples](https://github.com/elast0ny/shared_memory-rs/tree/master/examples) ! + +use std::fs::{File, OpenOptions}; +use std::io::{ErrorKind, Read, Write}; + +use std::fs::remove_file; +use std::path::{Path, PathBuf}; + +use cfg_if::cfg_if; + +cfg_if! { + if #[cfg(feature = "logging")] { + pub use log; + } else { + #[allow(unused_macros)] + mod log { + macro_rules! trace (($($tt:tt)*) => {{}}); + macro_rules! debug (($($tt:tt)*) => {{}}); + macro_rules! info (($($tt:tt)*) => {{}}); + macro_rules! warn (($($tt:tt)*) => {{}}); + macro_rules! error (($($tt:tt)*) => {{}}); + pub(crate) use {debug, trace}; + } + } +} + +use crate::log::*; + +mod error; +pub use error::*; + +//Load up the proper OS implementation +cfg_if! { + if #[cfg(target_os="windows")] { + mod windows; + use windows as os_impl; + } else if #[cfg(any(target_os="freebsd", target_os="linux", target_os="macos"))] { + mod unix; + use crate::unix as os_impl; + } else { + compile_error!("shared_memory isnt implemented for this platform..."); + } +} + +#[derive(Clone, Default)] +/// Struct used to configure different parameters before creating a shared memory mapping +pub struct ShmemConf { + owner: bool, + os_id: Option, + overwrite_flink: bool, + flink_path: Option, + size: usize, + ext: os_impl::ShmemConfExt, +} +impl Drop for ShmemConf { + fn drop(&mut self) { + // Delete the flink if we are the owner of the mapping + if self.owner { + if let Some(flink_path) = self.flink_path.as_ref() { + debug!("Deleting file link {}", flink_path.to_string_lossy()); + let _ = remove_file(flink_path); + } + } + } +} + +impl ShmemConf { + /// Create a new default shmem config + pub fn new() -> Self { + ShmemConf::default() + } + /// Provide a specific os identifier for the mapping + /// + /// When not specified, a randomly generated identifier will be used + pub fn os_id>(mut self, os_id: S) -> Self { + self.os_id = Some(String::from(os_id.as_ref())); + self + } + + /// Overwrites file links if it already exist when calling `create()` + pub fn force_create_flink(mut self) -> Self { + self.overwrite_flink = true; + self + } + + /// Create the shared memory mapping with a file link + /// + /// This creates a file on disk that contains the unique os_id for the mapping. + /// This can be useful when application want to rely on filesystems to share mappings + pub fn flink>(mut self, path: S) -> Self { + self.flink_path = Some(PathBuf::from(path.as_ref())); + self + } + + /// Sets the size of the mapping that will be used in `create()` + pub fn size(mut self, size: usize) -> Self { + self.size = size; + self + } + + /// Create a new mapping using the current configuration + pub fn create(mut self) -> Result { + if self.size == 0 { + return Err(ShmemError::MapSizeZero); + } + + if let Some(ref flink_path) = self.flink_path { + if !self.overwrite_flink && flink_path.is_file() { + return Err(ShmemError::LinkExists); + } + } + + // Create the mapping + let mapping = match self.os_id { + None => { + // Generate random ID until one works + loop { + let cur_id = format!("/shmem_{:X}", rand::random::()); + match os_impl::create_mapping(&cur_id, self.size) { + Err(ShmemError::MappingIdExists) => continue, + Ok(m) => break m, + Err(e) => { + return Err(e); + } + }; + } + } + Some(ref specific_id) => os_impl::create_mapping(specific_id, self.size)?, + }; + debug!("Created shared memory mapping '{}'", mapping.unique_id); + + // Create flink + if let Some(ref flink_path) = self.flink_path { + debug!("Creating file link that points to mapping"); + let mut open_options: OpenOptions = OpenOptions::new(); + open_options.write(true); + + if self.overwrite_flink { + open_options.create(true).truncate(true); + } else { + open_options.create_new(true); + } + + match open_options.open(flink_path) { + Ok(mut f) => { + // write the shmem uid asap + if let Err(e) = f.write(mapping.unique_id.as_bytes()) { + let _ = std::fs::remove_file(flink_path); + return Err(ShmemError::LinkWriteFailed(e)); + } + } + Err(e) if e.kind() == ErrorKind::AlreadyExists => { + return Err(ShmemError::LinkExists) + } + Err(e) => return Err(ShmemError::LinkCreateFailed(e)), + } + + debug!( + "Created file link '{}' with id '{}'", + flink_path.to_string_lossy(), + mapping.unique_id + ); + } + + self.owner = true; + self.size = mapping.map_size; + + Ok(Shmem { + config: self, + mapping, + }) + } + + /// Opens an existing mapping using the current configuration + pub fn open(mut self) -> Result { + // Must at least have a flink or an os_id + if self.flink_path.is_none() && self.os_id.is_none() { + debug!("Open called with no file link or unique id..."); + return Err(ShmemError::NoLinkOrOsId); + } + + let mut flink_uid = String::new(); + let mut retry = 0; + loop { + let unique_id = if let Some(ref unique_id) = self.os_id { + retry = 5; + unique_id.as_str() + } else { + let flink_path = self.flink_path.as_ref().unwrap(); + debug!( + "Open shared memory from file link {}", + flink_path.to_string_lossy() + ); + let mut f = match File::open(flink_path) { + Ok(f) => f, + Err(e) => return Err(ShmemError::LinkOpenFailed(e)), + }; + flink_uid.clear(); + if let Err(e) = f.read_to_string(&mut flink_uid) { + return Err(ShmemError::LinkReadFailed(e)); + } + flink_uid.as_str() + }; + + match os_impl::open_mapping(unique_id, self.size, &self.ext) { + Ok(m) => { + self.size = m.map_size; + self.owner = false; + + return Ok(Shmem { + config: self, + mapping: m, + }); + } + // If we got this failing os_id from the flink, try again in case the shmem owner didnt write the full + // unique_id to the file + Err(ShmemError::MapOpenFailed(_)) if self.os_id.is_none() && retry < 5 => { + retry += 1; + std::thread::sleep(std::time::Duration::from_millis(50)); + } + Err(e) => return Err(e), + } + } + } +} + +/// Structure used to extract information from an existing shared memory mapping +pub struct Shmem { + config: ShmemConf, + mapping: os_impl::MapData, +} +#[allow(clippy::len_without_is_empty)] +impl Shmem { + /// Returns whether we created the mapping or not + pub fn is_owner(&self) -> bool { + self.config.owner + } + /// Allows for gaining/releasing ownership of the mapping + /// + /// Warning : You must ensure at least one process owns the mapping in order to ensure proper cleanup code is ran + pub fn set_owner(&mut self, is_owner: bool) -> bool { + self.mapping.set_owner(is_owner); + + let prev_val = self.config.owner; + self.config.owner = is_owner; + prev_val + } + /// Returns the OS unique identifier for the mapping + pub fn get_os_id(&self) -> &str { + self.mapping.unique_id.as_str() + } + /// Returns the flink path if present + pub fn get_flink_path(&self) -> Option<&PathBuf> { + self.config.flink_path.as_ref() + } + /// Returns the total size of the mapping + pub fn len(&self) -> usize { + self.mapping.map_size + } + /// Returns a raw pointer to the mapping + pub fn as_ptr(&self) -> *mut u8 { + self.mapping.as_mut_ptr() + } + /// Returns mapping as a byte slice + /// # Safety + /// This function is unsafe because it is impossible to ensure the range of bytes is immutable + pub unsafe fn as_slice(&self) -> &[u8] { + std::slice::from_raw_parts(self.as_ptr(), self.len()) + } + /// Returns mapping as a mutable byte slice + /// # Safety + /// This function is unsafe because it is impossible to ensure the returned mutable refence is unique/exclusive + pub unsafe fn as_slice_mut(&mut self) -> &mut [u8] { + std::slice::from_raw_parts_mut(self.as_ptr(), self.len()) + } +} diff --git a/crates/vendor_shared_memory/src/unix.rs b/crates/vendor_shared_memory/src/unix.rs new file mode 100644 index 00000000..3c7fbb7a --- /dev/null +++ b/crates/vendor_shared_memory/src/unix.rs @@ -0,0 +1,225 @@ +use std::num::NonZeroUsize; +use std::os::unix::io::{BorrowedFd, IntoRawFd, RawFd}; +use std::ptr::{null_mut, NonNull}; + +use crate::log::*; +use nix::fcntl::OFlag; +use nix::sys::mman::{mmap, munmap, shm_open, shm_unlink, MapFlags, ProtFlags}; +use nix::sys::stat::{fstat, Mode}; +use nix::unistd::{close, ftruncate}; + +use crate::ShmemError; + +#[derive(Clone, Default)] +pub struct ShmemConfExt; + +pub struct MapData { + //On linux, you must shm_unlink() the object created for the mapping. It wont disappear automatically. + owner: bool, + + //File descriptor to our open mapping + map_fd: RawFd, + + //Shared mapping uid + pub unique_id: String, + //Total size of the mapping + pub map_size: usize, + //Pointer to the first address of our mapping + pub map_ptr: *mut u8, +} + +impl MapData { + pub fn as_mut_ptr(&self) -> *mut u8 { + self.map_ptr + } +} + +/// Shared memory teardown for linux +impl Drop for MapData { + ///Takes care of properly closing the SharedMem (munmap(), shmem_unlink(), close()) + fn drop(&mut self) { + //Unmap memory + if !self.map_ptr.is_null() { + trace!( + "munmap(map_ptr:{:p},map_size:{})", + self.map_ptr, + self.map_size + ); + if let Err(_e) = unsafe { + munmap(NonNull::new_unchecked(self.map_ptr as *mut _), self.map_size) + } { + debug!("Failed to munmap() shared memory mapping : {}", _e); + }; + } + + //Unlink shmem + if self.map_fd != 0 { + //unlink shmem if we created it + if self.owner { + debug!("Deleting persistent mapping"); + trace!("shm_unlink({})", self.unique_id.as_str()); + if let Err(_e) = shm_unlink(self.unique_id.as_str()) { + debug!("Failed to shm_unlink() shared memory : {}", _e); + }; + } + + trace!("close({})", self.map_fd); + if let Err(_e) = close(self.map_fd) { + debug!( + "os_impl::Linux : Failed to close() shared memory file descriptor : {}", + _e + ); + }; + } + } +} + +impl MapData { + pub fn set_owner(&mut self, is_owner: bool) -> bool { + let prev_val = self.owner; + self.owner = is_owner; + prev_val + } +} + +/// Creates a mapping specified by the uid and size +pub fn create_mapping(unique_id: &str, map_size: usize) -> Result { + //Create shared memory file descriptor + debug!("Creating persistent mapping at {}", unique_id); + let shmem_fd = match shm_open( + unique_id, //Unique name that usualy pops up in /dev/shm/ + OFlag::O_CREAT | OFlag::O_EXCL | OFlag::O_RDWR, //create exclusively (error if collision) and read/write to allow resize + Mode::S_IRUSR | Mode::S_IWUSR, //Permission allow user+rw + ) { + Ok(v) => { + let v = v.into_raw_fd(); + trace!( + "shm_open({}, {:X}, {:X}) == {}", + unique_id, + OFlag::O_CREAT | OFlag::O_EXCL | OFlag::O_RDWR, + Mode::S_IRUSR | Mode::S_IWUSR, + v + ); + v + } + Err(nix::Error::EEXIST) => return Err(ShmemError::MappingIdExists), + Err(e) => return Err(ShmemError::MapCreateFailed(e as u32)), + }; + + let mut new_map: MapData = MapData { + owner: true, + unique_id: String::from(unique_id), + map_fd: shmem_fd, + map_size, + map_ptr: null_mut(), + }; + + //Enlarge the memory descriptor file size to the requested map size + debug!("Creating memory mapping"); + trace!("ftruncate({}, {})", new_map.map_fd, new_map.map_size); + match ftruncate( + unsafe { BorrowedFd::borrow_raw(new_map.map_fd) }, + new_map.map_size as _, + ) { + Ok(_) => {} + Err(e) => return Err(ShmemError::UnknownOsError(e as u32)), + }; + + //Put the mapping in our address space + debug!("Loading mapping into address space"); + new_map.map_ptr = match unsafe { + mmap( + None, //Desired addr + NonZeroUsize::new(new_map.map_size).unwrap(), //size of mapping + ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, //Permissions on pages + MapFlags::MAP_SHARED, //What kind of mapping + BorrowedFd::borrow_raw(new_map.map_fd), //fd + 0, //Offset into fd + ) + } { + Ok(v) => { + trace!( + "mmap(NULL, {}, {:X}, {:X}, {}, 0) == {:p}", + new_map.map_size, + ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, + MapFlags::MAP_SHARED, + new_map.map_fd, + v + ); + v.as_ptr() as *mut _ + } + Err(e) => return Err(ShmemError::MapCreateFailed(e as u32)), + }; + + Ok(new_map) +} + +/// Opens an existing mapping specified by its uid +pub fn open_mapping( + unique_id: &str, + _map_size: usize, + _ext: &ShmemConfExt, +) -> Result { + //Open shared memory + debug!("Openning persistent mapping at {}", unique_id); + let shmem_fd = match shm_open( + unique_id, + OFlag::O_RDWR, //Open read write + Mode::S_IRUSR, + ) { + Ok(v) => { + let v = v.into_raw_fd(); + trace!( + "shm_open({}, {:X}, {:X}) == {}", + unique_id, + OFlag::O_RDWR, + Mode::S_IRUSR, + v + ); + v + } + Err(e) => return Err(ShmemError::MapOpenFailed(e as u32)), + }; + + let mut new_map: MapData = MapData { + owner: false, + unique_id: String::from(unique_id), + map_fd: shmem_fd, + map_size: 0, + map_ptr: null_mut(), + }; + + //Get mmap size + new_map.map_size = match fstat(unsafe { BorrowedFd::borrow_raw(new_map.map_fd) }) { + Ok(v) => v.st_size as usize, + Err(e) => return Err(ShmemError::MapOpenFailed(e as u32)), + }; + + //Map memory into our address space + debug!("Loading mapping into address space"); + new_map.map_ptr = match unsafe { + mmap( + None, //Desired addr + NonZeroUsize::new(new_map.map_size).unwrap(), //size of mapping + ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, //Permissions on pages + MapFlags::MAP_SHARED, //What kind of mapping + BorrowedFd::borrow_raw(new_map.map_fd), //fd + 0, //Offset into fd + ) + } { + Ok(v) => { + trace!( + "mmap(NULL, {}, {:X}, {:X}, {}, 0) == {:p}", + new_map.map_size, + ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, + MapFlags::MAP_SHARED, + new_map.map_fd, + v + ); + v.as_ptr() as *mut _ + } + Err(e) => return Err(ShmemError::MapOpenFailed(e as u32)), + }; + + Ok(new_map) +} diff --git a/crates/vendor_shared_memory/src/windows.rs b/crates/vendor_shared_memory/src/windows.rs new file mode 100644 index 00000000..96f4a59f --- /dev/null +++ b/crates/vendor_shared_memory/src/windows.rs @@ -0,0 +1,281 @@ +use std::fs::{File, OpenOptions}; +use std::io::ErrorKind; +use std::os::windows::{fs::OpenOptionsExt, io::AsRawHandle}; +use std::path::PathBuf; + +use crate::{log::*, ShmemConf}; +use win_sys::*; + +use crate::ShmemError; + +#[derive(Clone, Default)] +pub struct ShmemConfExt { + allow_raw: bool, +} + +impl ShmemConf { + /// If set to true, enables openning raw shared memory that is not managed by this crate + pub fn allow_raw(mut self, allow: bool) -> Self { + self.ext.allow_raw = allow; + self + } +} + +pub struct MapData { + owner: bool, + + /// Pointer to the first byte of our mapping + /// Keep this above `file_map` so it gets dropped first + pub view: ViewOfFile, + + /// The handle to our open mapping + #[allow(dead_code)] + file_map: FileMapping, + + /// This file is used for shmem persistence. When an owner wants to drop the mapping, + /// it opens the file with FILE_FLAG_DELETE_ON_CLOSE, renames the file and closes it. + /// This makes it so future calls to open the old mapping will fail (as it was renamed) and + /// deletes the renamed file once all handles have been closed. + #[allow(dead_code)] + persistent_file: Option, + + //Shared mapping uid + pub unique_id: String, + //Total size of the mapping + pub map_size: usize, +} +///Teardown UnmapViewOfFile and close CreateMapping handle +impl Drop for MapData { + ///Takes care of properly closing the SharedMem + fn drop(&mut self) { + // Inspired by the boost implementation at + // https://github.com/boostorg/interprocess/blob/140b50efb3281fa3898f3a4cf939cfbda174718f/include/boost/interprocess/detail/win32_api.hpp + // Emulate POSIX behavior by + // 1. Opening the mmapped file with `FILE_FLAG_DELETE_ON_CLOSE`, causing it to be + // deleted when all its handles have been closed. + // 2. Renaming the mmapped file to prevent future access/opening. + // Once this has run, existing file/mapping handles remain usable but the file is + // deleted once all handles have been closed and no new handles can be opened + // because the file has been renamed. This matches the behavior of shm_unlink() + // on unix. + if self.owner { + let mut base_path = get_tmp_dir().unwrap(); + + // 1. Set file attributes so that it deletes itself once everyone has closed it + let file_path = base_path.join(self.unique_id.trim_start_matches('/')); + debug!("Setting mapping to delete after everyone has closed it"); + match OpenOptions::new() + .access_mode(GENERIC_READ | GENERIC_WRITE | DELETE) + .share_mode((FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE).0) + .create(false) + .attributes((FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE).0) + .open(&file_path) + { + Ok(_) => { + // 2. Rename file to prevent further use + base_path.push(&format!( + "{}_deleted", + self.unique_id.trim_start_matches('/') + )); + debug!( + "Renaming {} to {}", + file_path.to_string_lossy(), + base_path.to_string_lossy() + ); + if let Err(_e) = std::fs::rename(&file_path, &base_path) { + debug!( + "Failed to rename persistent_file {} : {}", + file_path.to_string_lossy(), + _e + ); + } + } + Err(_e) => { + debug!( + "Failed to set DELETE_ON_CLOSE on persistent_file {} : {}", + file_path.to_string_lossy(), + _e + ); + } + }; + } + } +} + +impl MapData { + pub fn set_owner(&mut self, is_owner: bool) -> bool { + let prev_val = self.owner; + self.owner = is_owner; + prev_val + } + pub fn as_mut_ptr(&self) -> *mut u8 { + self.view.as_mut_ptr() as _ + } +} + +/// Returns the path to a temporary directory in which to store files backing the shared memory. If it +/// doesn't exist, the directory is created. +fn get_tmp_dir() -> Result { + debug!("Getting & creating shared_memory-rs temp dir"); + let mut path = std::env::temp_dir(); + path.push("shared_memory-rs"); + + if path.is_dir() { + return Ok(path); + } + + match std::fs::create_dir_all(path.as_path()) { + Ok(_) => Ok(path), + Err(e) if e.kind() == ErrorKind::AlreadyExists => Ok(path), + Err(e) => Err(ShmemError::UnknownOsError(e.raw_os_error().unwrap() as _)), + } +} + +fn new_map( + unique_id: &str, + mut map_size: usize, + create: bool, + allow_raw: bool, +) -> Result { + // Create file to back the shared memory + let mut file_path = get_tmp_dir()?; + file_path.push(unique_id.trim_start_matches('/')); + debug!( + "{} persistent_file at {}", + if create { "Creating" } else { "Openning" }, + file_path.to_string_lossy() + ); + + let mut opt = OpenOptions::new(); + opt.read(true) + .write(true) + .share_mode((FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE).0) + .attributes((FILE_ATTRIBUTE_TEMPORARY).0); + if create { + opt.create_new(true); + } else { + opt.create(false); + }; + + let mut persistent_file = None; + let map_h = match opt.open(&file_path) { + Ok(f) => { + //Create/open Mapping using persistent file + debug!( + "{} memory mapping", + if create { "Creating" } else { "Openning" }, + ); + let high_size: u32 = ((map_size as u64 & 0xFFFF_FFFF_0000_0000_u64) >> 32) as u32; + let low_size: u32 = (map_size as u64 & 0xFFFF_FFFF_u64) as u32; + trace!( + "CreateFileMapping({:?}, NULL, {:X}, {}, {}, '{}')", + HANDLE(f.as_raw_handle() as _), + PAGE_READWRITE.0, + high_size, + low_size, + unique_id, + ); + + match CreateFileMapping( + HANDLE(f.as_raw_handle() as _), + None, + PAGE_READWRITE, + high_size, + low_size, + unique_id, + ) { + Ok(v) => { + persistent_file = Some(f); + v + } + Err(e) => { + let err_code = e.win32_error().unwrap(); + return if err_code == ERROR_ALREADY_EXISTS { + Err(ShmemError::MappingIdExists) + } else { + Err(if create { + ShmemError::MapCreateFailed(err_code.0) + } else { + ShmemError::MapOpenFailed(err_code.0) + }) + }; + } + } + } + Err(e) if e.kind() == ErrorKind::AlreadyExists => return Err(ShmemError::MappingIdExists), + Err(e) => { + if create { + return Err(ShmemError::MapCreateFailed(e.raw_os_error().unwrap() as _)); + } else if !allow_raw { + return Err(ShmemError::MapOpenFailed(ERROR_FILE_NOT_FOUND.0)); + } + + // This may be a mapping that isnt managed by this crate + // Try to open the mapping without any backing file + trace!( + "OpenFileMappingW({:?}, {}, '{}')", + FILE_MAP_ALL_ACCESS, + false, + unique_id, + ); + match OpenFileMapping(FILE_MAP_ALL_ACCESS, false, unique_id) { + Ok(h) => h, + Err(e) => { + return Err(ShmemError::MapOpenFailed(e.win32_error().unwrap().0)); + } + } + } + }; + trace!("0x{:X}", map_h); + + //Map mapping into address space + debug!("Loading mapping into address space"); + trace!( + "MapViewOfFile(0x{:X}, {:X}, 0, 0, 0)", + map_h, + (FILE_MAP_READ | FILE_MAP_WRITE).0, + ); + let map_ptr = match MapViewOfFile(map_h.as_handle(), FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0) { + Ok(v) => v, + Err(e) => { + return Err(if create { + ShmemError::MapCreateFailed(e.win32_error().unwrap().0) + } else { + ShmemError::MapOpenFailed(e.win32_error().unwrap().0) + }) + } + }; + trace!("\t{:p}", map_ptr); + + if !create { + //Get the real size of the openned mapping + let mut info = MEMORY_BASIC_INFORMATION::default(); + if let Err(e) = VirtualQuery(map_ptr.as_mut_ptr(), &mut info) { + return Err(ShmemError::UnknownOsError(e.win32_error().unwrap().0)); + } + map_size = info.RegionSize; + } + + Ok(MapData { + owner: create, + file_map: map_h, + persistent_file, + unique_id: unique_id.to_string(), + map_size, + view: map_ptr, + }) +} + +//Creates a mapping specified by the uid and size +pub fn create_mapping(unique_id: &str, map_size: usize) -> Result { + new_map(unique_id, map_size, true, false) +} + +//Opens an existing mapping specified by its uid +pub fn open_mapping( + unique_id: &str, + map_size: usize, + ext: &ShmemConfExt, +) -> Result { + new_map(unique_id, map_size, false, ext.allow_raw) +} From cd23c1bd3351d2ce423a6abc088e50fd9175c603 Mon Sep 17 00:00:00 2001 From: social4hyq Date: Sun, 7 Jun 2026 13:26:00 +0800 Subject: [PATCH 2/3] feat(fspy_preload_unix): route execveat through SYS_execveat syscall on OHOS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OHOS musl libc.so does not export the `execveat` dynamic symbol, so two places in the LD_PRELOAD interceptor break on aarch64-unknown-linux-ohos: 1. The intercept!() macro's compile-time signature check references `crate::libc::execveat`, which on OHOS resolves through the `libc` crate's gnu-only binding — missing → fails to compile. 2. At runtime, `execveat::original()` does dlsym(RTLD_NEXT, "execveat"), which returns NULL on OHOS → calling the resulting function pointer segfaults. Add a syscall-backed shim in `crate::libc` cfg-gated to target_env = "ohos" so the macro's signature check resolves, and at the interceptor's fallthrough site replace `execveat::original()` with the shim on the same cfg. `SYS_execveat` is reliably defined for aarch64-linux-ohos (libc 0.2 includes the constant); the preflight `c2_sys_execveat` probe confirms the syscall works on both ci-runner OHOS and HarmonyOS userspace. Permanently locked: the preflight `c1_execveat_sym` probe fails on both tracks, so the extern-decl path can never work on OHOS musl. This commit is the only safe route as long as upstream `libc` does not add an OHOS binding. --- .../src/interceptions/spawn/exec/mod.rs | 18 ++++++++++++++-- crates/fspy_preload_unix/src/libc.rs | 21 +++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/crates/fspy_preload_unix/src/interceptions/spawn/exec/mod.rs b/crates/fspy_preload_unix/src/interceptions/spawn/exec/mod.rs index 182226ee..9027c117 100644 --- a/crates/fspy_preload_unix/src/interceptions/spawn/exec/mod.rs +++ b/crates/fspy_preload_unix/src/interceptions/spawn/exec/mod.rs @@ -206,8 +206,22 @@ mod linux_only { }; let abs_path = match abs_path_result { Ok(None) => { - // SAFETY: forwarding the original arguments to the real execveat syscall - return unsafe { execveat::original()(dirfd, pathname, argv, envp, flags) }; + // OHOS musl libc does not export execveat, so dlsym(RTLD_NEXT) + // returns NULL and execveat::original() would dereference null. + // Drop straight to the syscall — see crate::libc::execveat for the + // empirical preflight evidence (c1_execveat_sym fail / c2_sys_execveat pass). + #[cfg(target_env = "ohos")] + { + // SAFETY: forwarding the original execveat arguments through the raw syscall + return unsafe { + crate::libc::execveat(dirfd, pathname, argv, envp, flags) + }; + } + #[cfg(not(target_env = "ohos"))] + { + // SAFETY: forwarding the original arguments to the real execveat syscall + return unsafe { execveat::original()(dirfd, pathname, argv, envp, flags) }; + } } Ok(Some(path)) => path.as_ptr(), Err(errno) => { diff --git a/crates/fspy_preload_unix/src/libc.rs b/crates/fspy_preload_unix/src/libc.rs index 8d5d2d0a..99dd4170 100644 --- a/crates/fspy_preload_unix/src/libc.rs +++ b/crates/fspy_preload_unix/src/libc.rs @@ -1,5 +1,26 @@ pub use libc::*; +// OHOS musl libc does not export the `execveat` dynamic symbol, and the `libc` +// crate (0.2.x) only declares the binding under glibc. Provide a syscall-backed +// shim so the LD_PRELOAD interceptor's compile-time signature check and the +// fallthrough call site both resolve. `SYS_execveat` is reliably defined for +// aarch64-unknown-linux-ohos; the preflight `c2_sys_execveat` probe verifies +// the syscall works on both ci-runner and HarmonyOS userspace. +#[cfg(target_env = "ohos")] +pub unsafe extern "C" fn execveat( + dirfd: c_int, + pathname: *const c_char, + argv: *const *mut c_char, + envp: *const *mut c_char, + flags: c_int, +) -> c_int { + // SAFETY: the caller upholds POSIX execveat preconditions on the pointers; + // libc::syscall is the only stable way to invoke this on OHOS musl + unsafe { + libc::syscall(libc::SYS_execveat, dirfd, pathname, argv, envp, flags) as c_int + } +} + unsafe extern "C" { // On macOS x86_64, directory functions use $INODE64 symbol suffix for 64-bit inode support. // On arm64, 64-bit inodes are the only option so no suffix is needed. From d5491df39cdb6cad07789fcf48990783f29423ae Mon Sep 17 00:00:00 2001 From: social4hyq Date: Sun, 7 Jun 2026 13:35:36 +0800 Subject: [PATCH 3/3] ci: gate OHOS compile surface with cargo check on aarch64-unknown-linux-ohos aarch64-unknown-linux-ohos is a rustc tier-3 target. The existing clippy job runs against the linux-gnu host, so OHOS-cfg-gated code paths (vendor shared_memory's nix 0.30 patch, fspy_preload_unix's execveat syscall shim, and the `target_env = "ohos"` branches in the unix-shared crates) never see type-checking from CI. Add a `check-ohos` job that: * pulls the workspace-pinned nightly toolchain via setup-rust; * adds the aarch64-unknown-linux-ohos rustup target; * runs `cargo check --locked --target aarch64-unknown-linux-ohos` scoped to `shared_memory`, `fspy_shared`, `fspy_shared_unix`, and `fspy_preload_unix` with `RUSTFLAGS=-D warnings`. The check is package-scoped rather than `--workspace` because `vite_task`'s build script downloads host-arch binaries that are unrelated to the OHOS cfg surface, and expanding scope would add brittleness without surfacing OHOS-specific code paths. The job is wired into the `done` aggregator so PRs cannot merge while the OHOS surface fails to type-check. This is a check-only job: no `cargo build`, no test execution. OHOS lacks a GitHub Actions runner with a HarmonyOS kernel, so end-to-end smoke tests remain the responsibility of HarmonyBrew's downstream verification. --- .github/workflows/ci.yml | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b8e819e3..a3d715a6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -245,6 +245,45 @@ jobs: - name: Run ignored tests run: cargo test -- --ignored + check-ohos: + needs: detect-changes + if: needs.detect-changes.outputs.code-changed == 'true' + name: Check OHOS (aarch64-unknown-linux-ohos) + runs-on: namespace-profile-linux-x64-default + steps: + - uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2 + + - name: Update submodules + run: git submodule update --init --recursive + + - uses: oxc-project/setup-rust@3d6fb132fbe7cdcb66bf8ec193911c2945369d12 # v1.0.17 + with: + save-cache: ${{ github.ref_name == 'main' }} + cache-key: check-ohos + + - run: rustup target add aarch64-unknown-linux-ohos + + # Tier-3 target metadata check for the OHOS-cfg-gated surface: + # * vendor shared_memory (nix 0.30 patch — upstream nix 0.23 fails to + # gate ohos-incompatible symbols) + # * fspy_preload_unix (LD_PRELOAD lib; execveat is reached through a + # syscall shim because OHOS musl libc does not export the dynamic + # symbol) + # * fspy_shared / fspy_shared_unix (consumers) + # Scoped by package rather than --workspace because vite_task pulls a + # build script that downloads host-arch binaries unrelated to the OHOS + # cfg surface; expanding scope would only add brittleness without + # surfacing OHOS-specific code paths. + - name: cargo check OHOS surface + run: >- + cargo check --locked --target aarch64-unknown-linux-ohos + -p shared_memory + -p fspy_shared + -p fspy_shared_unix + -p fspy_preload_unix + env: + RUSTFLAGS: -D warnings + fmt: name: Format and Check Deps runs-on: namespace-profile-linux-x64-default @@ -287,6 +326,7 @@ jobs: - clippy - test - test-musl + - check-ohos - fmt steps: - run: exit 1