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
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ feat_common_core = [
"dircolors",
"dirname",
"echo",
"env",
"expand",
"expr",
"factor",
Expand Down Expand Up @@ -195,7 +196,6 @@ feat_Tier1 = [
"feat_common_core",
"df",
"du",
"env",
"hostname",
"kill",
"nohup",
Expand Down
52 changes: 50 additions & 2 deletions src/uu/env/src/native_int_str.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Comment on lines +31 to +32
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<u8>) -> Self;
fn into_vec(self) -> Vec<u8>;
}

impl OsStringExt for OsString {
fn from_vec(vec: Vec<u8>) -> 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<u8> {
self.into_encoded_bytes()
}
}
}

#[cfg(not(windows))]
use u8 as NativeIntCharU;
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/uucore/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
23 changes: 20 additions & 3 deletions src/uucore/src/lib/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Vec<OsString>> = LazyLock::new(|| wild::args_os().collect());
#[cfg(not(windows))]
#[cfg(all(not(windows), not(target_os = "wasi")))]
static ARGV: LazyLock<Vec<OsString>> = 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<Vec<OsString>> = LazyLock::new(|| {
let argv: Vec<OsString> = std::env::args_os().collect();
if argv.is_empty() {
vec![OsString::from("uu")]
Comment on lines +377 to +381
} else {
argv
}
});

static UTIL_NAME: LazyLock<String> = 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")
Expand Down
Loading