diff --git a/Cargo.toml b/Cargo.toml index 7107cd8a240..5c6636a7582 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -137,6 +137,7 @@ feat_common_core = [ "dircolors", "dirname", "echo", + "env", "expand", "expr", "factor", @@ -195,7 +196,6 @@ feat_Tier1 = [ "feat_common_core", "df", "du", - "env", "hostname", "kill", "nohup", diff --git a/src/uu/env/src/native_int_str.rs b/src/uu/env/src/native_int_str.rs index 2f289f3a874..e4e9e043527 100644 --- a/src/uu/env/src/native_int_str.rs +++ b/src/uu/env/src/native_int_str.rs @@ -15,11 +15,52 @@ use std::ffi::OsString; #[cfg(unix)] use std::os::unix::ffi::{OsStrExt, OsStringExt}; -#[cfg(target_os = "wasi")] +#[cfg(all(target_os = "wasi", target_env = "p1"))] use std::os::wasi::ffi::{OsStrExt, OsStringExt}; #[cfg(windows)] use std::os::windows::prelude::*; use std::{borrow::Cow, ffi::OsStr}; +#[cfg(all(target_os = "wasi", not(target_env = "p1")))] +use wasip2_ffi::{OsStrExt, OsStringExt}; + +// On wasm32-wasip2 `std::os::wasi::ffi` is behind the unstable `wasip2` library feature, so the +// byte views come from the stable encoded-bytes API instead. The component model defines every +// string the host hands a guest as UTF-8, so on this target the encoded bytes *are* the bytes, and +// the reverse direction can go through `str` without `unsafe` — the same approach as +// `uucore::os_str_from_bytes` / `os_string_from_vec` on this target. +#[cfg(all(target_os = "wasi", not(target_env = "p1")))] +mod wasip2_ffi { + use std::ffi::{OsStr, OsString}; + + pub trait OsStrExt { + fn as_bytes(&self) -> &[u8]; + } + + impl OsStrExt for OsStr { + fn as_bytes(&self) -> &[u8] { + self.as_encoded_bytes() + } + } + + pub trait OsStringExt { + fn from_vec(vec: Vec) -> Self; + fn into_vec(self) -> Vec; + } + + impl OsStringExt for OsString { + fn from_vec(vec: Vec) -> Self { + match String::from_utf8(vec) { + Ok(s) => s.into(), + // Cannot have come from the host; keep going rather than abort the component. + Err(e) => String::from_utf8_lossy(e.as_bytes()).into_owned().into(), + } + } + + fn into_vec(self) -> Vec { + self.into_encoded_bytes() + } + } +} #[cfg(not(windows))] use u8 as NativeIntCharU; @@ -153,13 +194,20 @@ pub fn from_native_int_representation(input: Cow<'_, NativeIntStr>) -> Cow<'_, O Cow::Owned(OsString::from_wide(&input)) } - #[cfg(not(windows))] + #[cfg(any(unix, all(target_os = "wasi", target_env = "p1")))] { match input { Cow::Borrowed(borrow) => Cow::Borrowed(OsStr::from_bytes(borrow)), Cow::Owned(own) => Cow::Owned(OsString::from_vec(own)), } } + + // wasip2: bytes → `OsStr` goes through `str` (see `wasip2_ffi`), so a borrowed input has to + // become an owned one. + #[cfg(all(target_os = "wasi", not(target_env = "p1")))] + { + Cow::Owned(OsString::from_vec(input.into_owned())) + } } #[allow(clippy::needless_pass_by_value)] // needed on windows diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index 05b9b0b156e..39d9594be55 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -36,7 +36,7 @@ libc = { workspace = true, optional = true } openssl = { workspace = true, optional = true } os_display = { workspace = true } rustc-hash = { workspace = true } -rustix = { workspace = true, optional = true } +rustix = { workspace = true, optional = true, features = ["std"] } # Not optional: os_display already pulls unicode-width into every uucore build, # so making it a direct dependency here is free and keeps char_width available # on all targets (a feature-gated optional dep failed to activate on wasm). diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index d4c703d081a..56d074d1ec0 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -367,13 +367,30 @@ pub fn set_utility_is_second_arg() { // So if we want only the first arg or so it's overkill. We cache it. #[cfg(windows)] static ARGV: LazyLock> = LazyLock::new(|| wild::args_os().collect()); -#[cfg(not(windows))] +#[cfg(all(not(windows), not(target_os = "wasi")))] static ARGV: LazyLock> = LazyLock::new(|| std::env::args_os().collect()); +// `std::env::args_os()` can be empty on wasi when the component is not invoked as a CLI +// command — for instance when it is embedded as a library and the host passes no argv at all. +// `UTIL_NAME`/`EXECUTION_PHRASE` and their callers index `ARGV[0]`, which panics on an empty +// vec, and a panic here aborts the whole component. Guarantee at least one element so those +// globals resolve to a stable fallback name instead. +#[cfg(all(not(windows), target_os = "wasi"))] +static ARGV: LazyLock> = LazyLock::new(|| { + let argv: Vec = std::env::args_os().collect(); + if argv.is_empty() { + vec![OsString::from("uu")] + } else { + argv + } +}); static UTIL_NAME: LazyLock = LazyLock::new(|| { - let base_index = usize::from(get_utility_is_second_arg()); + // Clamp every index into `ARGV`: on wasip2 the vec may be shorter than the multicall layout + // assumes (see the ARGV comment above), and an out-of-bounds index aborts the component. + let last = ARGV.len().saturating_sub(1); + let base_index = usize::from(get_utility_is_second_arg()).min(last); let is_man = usize::from(ARGV[base_index].eq("manpage")); - let argv_index = base_index + is_man; + let argv_index = (base_index + is_man).min(last); // Strip directory path to show only utility name // (e.g., "mkdir" instead of "./target/debug/mkdir")