From 69878ad2d795c93cd6a6abb6ada1fdd723129157 Mon Sep 17 00:00:00 2001 From: Jackkakaya Date: Fri, 24 Jul 2026 00:35:49 +0800 Subject: [PATCH 1/2] feat(core): support building for iOS targets brush-core currently fails to compile for aarch64-apple-ios / aarch64-apple-ios-sim, even though the crate's instance-scoped design (per-Shell working dir/env, fd-table-based I/O) otherwise works well inside an iOS app. Three small platform issues block the build: - uzers does not compile for iOS, and there is no user database inside the iOS app sandbox anyway. Gate the dependency to non-iOS unix and add a users_ios module that answers identity queries from uid/gid syscalls and the process environment. - nix does not expose waitid() on iOS; take the same waitpid() fallback already used for NetBSD/OpenBSD. - the ioctl(TIOCSCTTY) request argument is c_ulong on iOS just like macOS; widen the cfg from target_os = "macos" to target_vendor = "apple". No behavior change on any currently supported platform: all edits are cfg-gated to iOS except the ioctl cfg widening, which is identical on macOS. cargo check/clippy pass for aarch64-apple-ios, aarch64-apple-ios-sim, and the host. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011tLwxQ47SiGLGDDPXd8tzz --- brush-core/Cargo.toml | 2 + brush-core/src/sys/unix.rs | 4 ++ brush-core/src/sys/unix/commands.rs | 4 +- brush-core/src/sys/unix/signal.rs | 11 +++-- brush-core/src/sys/unix/users_ios.rs | 70 ++++++++++++++++++++++++++++ 5 files changed, 86 insertions(+), 5 deletions(-) create mode 100644 brush-core/src/sys/unix/users_ios.rs diff --git a/brush-core/Cargo.toml b/brush-core/Cargo.toml index d3f59d289..3b955fb43 100644 --- a/brush-core/Cargo.toml +++ b/brush-core/Cargo.toml @@ -77,6 +77,8 @@ nix = { version = "0.31.2", features = [ "user", ] } terminfo = "0.9.0" + +[target.'cfg(all(unix, not(target_os = "ios")))'.dependencies] uzers = "0.12.2" [target.wasm32-unknown-unknown.dependencies] diff --git a/brush-core/src/sys/unix.rs b/brush-core/src/sys/unix.rs index 77732c968..b022bd827 100644 --- a/brush-core/src/sys/unix.rs +++ b/brush-core/src/sys/unix.rs @@ -11,6 +11,10 @@ pub use crate::sys::tokio_process as process; pub mod resource; pub mod signal; pub mod terminal; +#[cfg(not(target_os = "ios"))] +pub(crate) mod users; +#[cfg(target_os = "ios")] +#[path = "unix/users_ios.rs"] pub(crate) mod users; /// Platform-specific errors. diff --git a/brush-core/src/sys/unix/commands.rs b/brush-core/src/sys/unix/commands.rs index b5554399a..bf2aa0a3b 100644 --- a/brush-core/src/sys/unix/commands.rs +++ b/brush-core/src/sys/unix/commands.rs @@ -88,9 +88,9 @@ fn pre_exec_lead_session() -> Result<(), std::io::Error> { ))); } - #[cfg(not(target_os = "macos"))] + #[cfg(not(target_vendor = "apple"))] let control = libc::TIOCSCTTY; - #[cfg(target_os = "macos")] + #[cfg(target_vendor = "apple")] let control: u64 = libc::TIOCSCTTY.into(); // SAFETY: diff --git a/brush-core/src/sys/unix/signal.rs b/brush-core/src/sys/unix/signal.rs index 36830d6a4..5611c9445 100644 --- a/brush-core/src/sys/unix/signal.rs +++ b/brush-core/src/sys/unix/signal.rs @@ -92,16 +92,21 @@ pub(crate) fn poll_for_stopped_children() -> Result { Ok(found_stopped) } -#[cfg(not(any(target_os = "macos", target_os = "netbsd", target_os = "openbsd")))] +#[cfg(not(any( + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "ios" +)))] fn waitid_all( flags: nix::sys::wait::WaitPidFlag, ) -> Result { nix::sys::wait::waitid(nix::sys::wait::Id::All, flags) } -// nix does not expose `waitid` on NetBSD/OpenBSD; `waitpid` for any child is +// nix does not expose `waitid` on NetBSD/OpenBSD/iOS; `waitpid` for any child is // equivalent for the flags used here. -#[cfg(any(target_os = "netbsd", target_os = "openbsd"))] +#[cfg(any(target_os = "netbsd", target_os = "openbsd", target_os = "ios"))] fn waitid_all( flags: nix::sys::wait::WaitPidFlag, ) -> Result { diff --git a/brush-core/src/sys/unix/users_ios.rs b/brush-core/src/sys/unix/users_ios.rs new file mode 100644 index 000000000..5f031263c --- /dev/null +++ b/brush-core/src/sys/unix/users_ios.rs @@ -0,0 +1,70 @@ +//! iOS: no user database is accessible from within the app sandbox; identity +//! comes from uid/gid syscalls and the process environment. + +use crate::error; +use std::path::PathBuf; + +pub(crate) const fn is_root() -> bool { + false +} + +pub(crate) fn get_user_home_dir(username: &str) -> Option { + // Only the current (sandboxed) user is resolvable. + if get_current_username().is_ok_and(|current| current == username) { + get_current_user_home_dir() + } else { + None + } +} + +pub(crate) fn get_current_user_home_dir() -> Option { + std::env::var_os("HOME").map(PathBuf::from) +} + +pub(crate) const fn get_current_user_default_shell() -> Option { + None +} + +#[expect(clippy::unnecessary_wraps)] +pub(crate) fn get_current_uid() -> Result { + // SAFETY: getuid is always successful and has no preconditions. + Ok(unsafe { libc::getuid() }) +} + +#[expect(clippy::unnecessary_wraps)] +pub(crate) fn get_current_gid() -> Result { + // SAFETY: getgid is always successful and has no preconditions. + Ok(unsafe { libc::getgid() }) +} + +#[expect(clippy::unnecessary_wraps)] +pub(crate) fn get_effective_uid() -> Result { + // SAFETY: geteuid is always successful and has no preconditions. + Ok(unsafe { libc::geteuid() }) +} + +#[expect(clippy::unnecessary_wraps)] +pub(crate) fn get_effective_gid() -> Result { + // SAFETY: getegid is always successful and has no preconditions. + Ok(unsafe { libc::getegid() }) +} + +#[expect(clippy::unnecessary_wraps)] +pub(crate) fn get_current_username() -> Result { + Ok(std::env::var("USER").unwrap_or_else(|_| "mobile".to_owned())) +} + +#[expect(clippy::unnecessary_wraps)] +pub(crate) fn get_user_group_ids() -> Result, error::Error> { + // SAFETY: getgid is always successful and has no preconditions. + Ok(vec![unsafe { libc::getgid() }]) +} + +pub(crate) fn get_all_users() -> Result, error::Error> { + Ok(vec![get_current_username()?]) +} + +#[expect(clippy::unnecessary_wraps)] +pub(crate) fn get_all_groups() -> Result, error::Error> { + Ok(vec!["mobile".to_owned()]) +} From b436ae19478b2e5cfdf8c9474c5dc1bc75c1f585 Mon Sep 17 00:00:00 2001 From: Jackkakaya Date: Fri, 24 Jul 2026 12:02:14 +0800 Subject: [PATCH 2/2] feat(core): expose ExecutionParameters::is_fd_specified Lets embedders distinguish a redirected/piped fd from an inherited one (e.g. tell a piped stdin from an interactive terminal). Co-Authored-By: Claude Fable 5 --- brush-core/src/interp.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/brush-core/src/interp.rs b/brush-core/src/interp.rs index ddf637f11..6fe519cd7 100644 --- a/brush-core/src/interp.rs +++ b/brush-core/src/interp.rs @@ -138,6 +138,15 @@ impl ExecutionParameters { } } + /// Returns whether the given file descriptor was explicitly specified for + /// this execution (e.g. via a redirection or pipeline), as opposed to being + /// inherited from the shell's persistent open files. Useful for telling a + /// redirected/piped stdin from an interactive one. + #[must_use] + pub fn is_fd_specified(&self, fd: ShellFd) -> bool { + matches!(self.open_files.fd_entry(fd), openfiles::OpenFileEntry::Open(_)) + } + /// Sets the given file descriptor to the provided open file. /// /// # Arguments