Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -287,6 +326,7 @@ jobs:
- clippy
- test
- test-musl
- check-ohos
- fmt
steps:
- run: exit 1
Expand Down
36 changes: 12 additions & 24 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand Down
18 changes: 16 additions & 2 deletions crates/fspy_preload_unix/src/interceptions/spawn/exec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
21 changes: 21 additions & 0 deletions crates/fspy_preload_unix/src/libc.rs
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
36 changes: 36 additions & 0 deletions crates/vendor_shared_memory/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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 <elast0ny00@gmail.com>"]
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"
47 changes: 47 additions & 0 deletions crates/vendor_shared_memory/README.md
Original file line number Diff line number Diff line change
@@ -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<NonZeroUsize>` 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 <https://github.com/elast0ny/shared_memory-rs>). When that happens:

1. In the workspace `Cargo.toml`, change
`shared_memory = { path = "crates/vendor_shared_memory" }` back to
`shared_memory = "<new-version>"`.
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 &lt;elast0ny00@gmail.com&gt;.
20 changes: 20 additions & 0 deletions crates/vendor_shared_memory/changelog.md
Original file line number Diff line number Diff line change
@@ -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`.
48 changes: 48 additions & 0 deletions crates/vendor_shared_memory/src/error.rs
Original file line number Diff line number Diff line change
@@ -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,
}
}
}
Loading