From 0da57244a6e4532e9c5697739eaf0205a6c919ff Mon Sep 17 00:00:00 2001 From: Aditya Salunkhe Date: Wed, 8 Jul 2026 20:58:34 +0530 Subject: [PATCH 01/11] uucore: don't index an empty argv on wasip2 (guests embedded as libraries have none) --- src/uucore/src/lib/lib.rs | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) 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") From d61c13967c96282ebe1dc04ff7e09529838de87a Mon Sep 17 00:00:00 2001 From: Aditya Salunkhe Date: Mon, 14 Sep 2026 01:48:49 +0530 Subject: [PATCH 02/11] uucore: enable rustix/std so its fs and io modules compile on wasm32-wasip2 --- src/uucore/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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). From 336894b3ddb30002d7f3a7c7b99b78c689e20070 Mon Sep 17 00:00:00 2001 From: Aditya Salunkhe Date: Mon, 14 Sep 2026 01:51:22 +0530 Subject: [PATCH 03/11] env: build on wasm32-wasip2 without the unstable std::os::wasi::ffi (encoded-bytes conversions) --- src/uu/env/src/native_int_str.rs | 52 ++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) 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 From c4058d7680a238ec79c46fb0f0eaab929e9175f6 Mon Sep 17 00:00:00 2001 From: Aditya Salunkhe Date: Mon, 14 Sep 2026 19:58:19 +0530 Subject: [PATCH 04/11] env: return to feat_common_core now that it builds on wasm32-wasip1 and wasip2 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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", From 17db4302d0fde2274e6e36f4fdfa0d683db514e3 Mon Sep 17 00:00:00 2001 From: Aditya Salunkhe Date: Sun, 20 Sep 2026 01:15:41 +0530 Subject: [PATCH 05/11] test(wasi): cover p2 conversions and empty argv Run env encoded-byte conversion tests under wasm32-wasip2, and factor the WASI argv fallback so its empty-input behavior is covered on host and target tests. --- .github/workflows/wasi.yml | 8 ++++++++ src/uu/env/src/native_int_str.rs | 27 +++++++++++++++++++++++++++ src/uucore/src/lib/lib.rs | 30 ++++++++++++++++++++++-------- 3 files changed, 57 insertions(+), 8 deletions(-) diff --git a/.github/workflows/wasi.yml b/.github/workflows/wasi.yml index 99df73994c4..8a9ddf7cc38 100644 --- a/.github/workflows/wasi.yml +++ b/.github/workflows/wasi.yml @@ -78,3 +78,11 @@ jobs: test_tail:: test_tee:: test_test:: test_touch:: test_tr:: \ test_true:: test_truncate:: \ test_unexpand:: test_unlink:: test_wc:: test_yes:: + - name: Test env conversions and argv fallback on WASI Preview 2 + if: matrix.job.target == 'wasm32-wasip2' + env: + CARGO_TARGET_WASM32_WASIP2_RUNNER: wasmtime + run: | + cargo test --target wasm32-wasip2 --no-default-features -p uu_env + cargo test --target wasm32-wasip2 --no-default-features -p uucore --lib \ + wasi_argv_fallback_prevents_empty_argv diff --git a/src/uu/env/src/native_int_str.rs b/src/uu/env/src/native_int_str.rs index e4e9e043527..7fa044e5704 100644 --- a/src/uu/env/src/native_int_str.rs +++ b/src/uu/env/src/native_int_str.rs @@ -360,3 +360,30 @@ impl<'a> NativeStr<'a> { } } } + +#[cfg(all(test, target_os = "wasi", not(target_env = "p1")))] +mod tests { + use super::*; + + #[test] + fn wasip2_encoded_bytes_round_trip() { + let source = OsString::from("NAME=Grüße"); + + let borrowed = to_native_int_representation(&source); + assert_eq!(&*borrowed, "NAME=Grüße".as_bytes()); + let converted = from_native_int_representation(borrowed); + assert_eq!(&*converted, source.as_os_str()); + + let owned: Cow<'static, NativeIntStr> = NCvt::convert(source.clone()); + assert_eq!( + from_native_int_representation_owned(owned.into_owned()), + source + ); + } + + #[test] + fn wasip2_invalid_encoded_bytes_are_lossy() { + let converted = from_native_int_representation_owned(vec![b'f', b'o', 0x80, b'o']); + assert_eq!(converted, OsString::from("fo\u{fffd}o")); + } +} diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 56d074d1ec0..067ebdaa303 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -369,20 +369,23 @@ pub fn set_utility_is_second_arg() { static ARGV: LazyLock> = LazyLock::new(|| wild::args_os().collect()); #[cfg(all(not(windows), not(target_os = "wasi")))] static ARGV: LazyLock> = LazyLock::new(|| std::env::args_os().collect()); + +#[cfg(any(test, target_os = "wasi"))] +fn with_wasi_argv_fallback(mut argv: Vec) -> Vec { + if argv.is_empty() { + argv.push(OsString::from("uu")); + } + argv +} + // `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 ARGV: LazyLock> = + LazyLock::new(|| with_wasi_argv_fallback(std::env::args_os().collect())); static UTIL_NAME: LazyLock = LazyLock::new(|| { // Clamp every index into `ARGV`: on wasip2 the vec may be shorter than the multicall layout @@ -736,6 +739,17 @@ mod tests { ] } + #[test] + fn wasi_argv_fallback_prevents_empty_argv() { + assert_eq!( + with_wasi_argv_fallback(Vec::new()), + vec![OsString::from("uu")] + ); + + let argv = vec![OsString::from("env"), OsString::from("--version")]; + assert_eq!(with_wasi_argv_fallback(argv.clone()), argv); + } + #[cfg(any(unix, target_os = "redox"))] fn test_invalid_utf8_args_lossy(os_str: &OsStr) { // assert our string is invalid utf8 From d0cde09ab729002e451507b1c8a51ffe2262efdd Mon Sep 17 00:00:00 2001 From: Aditya Salunkhe Date: Sun, 20 Sep 2026 17:07:50 +0530 Subject: [PATCH 06/11] comment reduction --- src/uu/env/src/native_int_str.rs | 7 ++----- src/uucore/src/lib/lib.rs | 6 +----- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/uu/env/src/native_int_str.rs b/src/uu/env/src/native_int_str.rs index 7fa044e5704..d2af737dd18 100644 --- a/src/uu/env/src/native_int_str.rs +++ b/src/uu/env/src/native_int_str.rs @@ -23,11 +23,8 @@ 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. +// WASI Preview 2 uses stable encoded-byte APIs because `std::os::wasi::ffi` is unstable. +// Component-model strings are UTF-8, so decoding through `str` preserves host values. #[cfg(all(target_os = "wasi", not(target_env = "p1")))] mod wasip2_ffi { use std::ffi::{OsStr, OsString}; diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 5ca78f2541b..adb1fba5936 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -393,11 +393,7 @@ fn with_wasi_argv_fallback(mut argv: Vec) -> Vec { argv } -// `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. +// WASI embeddings may omit argv; provide a stable utility-name fallback. #[cfg(all(not(windows), target_os = "wasi"))] static ARGV: LazyLock> = LazyLock::new(|| with_wasi_argv_fallback(std::env::args_os().collect())); From abb54ec7993bc58bac04225baaae88cef2d7934b Mon Sep 17 00:00:00 2001 From: Aditya Salunkhe Date: Mon, 21 Sep 2026 12:59:23 +0530 Subject: [PATCH 07/11] add fs feature ci check --- .github/workflows/wasi.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/wasi.yml b/.github/workflows/wasi.yml index 30600e821a2..bd11aea0685 100644 --- a/.github/workflows/wasi.yml +++ b/.github/workflows/wasi.yml @@ -84,5 +84,6 @@ jobs: CARGO_TARGET_WASM32_WASIP2_RUNNER: wasmtime run: | cargo test --target wasm32-wasip2 --no-default-features -p uu_env + cargo check --target wasm32-wasip2 --no-default-features -p uucore --features fs cargo test --target wasm32-wasip2 --no-default-features -p uucore --lib \ wasi_argv_fallback_prevents_empty_argv From b2305fef26c390915402ab75b77c6d34f0437813 Mon Sep 17 00:00:00 2001 From: Aditya Salunkhe Date: Wed, 23 Sep 2026 18:22:52 +0530 Subject: [PATCH 08/11] review fixes --- .github/workflows/wasi.yml | 13 +++-------- src/uu/env/src/native_int_str.rs | 38 ++++++++++++++++---------------- src/uucore/src/lib/lib.rs | 7 +++--- 3 files changed, 25 insertions(+), 33 deletions(-) diff --git a/.github/workflows/wasi.yml b/.github/workflows/wasi.yml index bd11aea0685..81ae7626ae2 100644 --- a/.github/workflows/wasi.yml +++ b/.github/workflows/wasi.yml @@ -46,7 +46,7 @@ jobs: CARGO_TARGET_WASM32_WASIP2_RUNNER: wasmtime run: | # Get all utilities and exclude ones that don't compile for ${{ matrix.job.target }} - EXCLUDE="df|du|env|more" + EXCLUDE="df|du|more" UTILS=$(./util/show-utils.sh | tr ' ' '\n' | grep -vE "^($EXCLUDE)$" | sed 's/^/-p uu_/' | tr '\n' ' ') cargo test --target ${{ matrix.job.target }} --no-default-features $UTILS - name: Run integration tests via wasmtime @@ -78,12 +78,5 @@ jobs: test_tail:: test_tee:: test_test:: test_touch:: test_tr:: \ test_true:: test_truncate:: \ test_unexpand:: test_unlink:: test_wc:: test_yes:: - - name: Test env conversions and argv fallback on WASI Preview 2 - if: matrix.job.target == 'wasm32-wasip2' - env: - CARGO_TARGET_WASM32_WASIP2_RUNNER: wasmtime - run: | - cargo test --target wasm32-wasip2 --no-default-features -p uu_env - cargo check --target wasm32-wasip2 --no-default-features -p uucore --features fs - cargo test --target wasm32-wasip2 --no-default-features -p uucore --lib \ - wasi_argv_fallback_prevents_empty_argv + - name: Check uucore fs feature builds standalone + run: cargo check --target ${{ matrix.job.target }} --no-default-features -p uucore --features fs diff --git a/src/uu/env/src/native_int_str.rs b/src/uu/env/src/native_int_str.rs index d2af737dd18..bcedf3a69de 100644 --- a/src/uu/env/src/native_int_str.rs +++ b/src/uu/env/src/native_int_str.rs @@ -3,10 +3,13 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +// spell-checker:ignore (words) Grüße + // This module contains classes and functions for dealing with the differences // between operating systems regarding the lossless processing of OsStr/OsString. // In contrast to existing crates with similar purpose, this module does not use any -// `unsafe` features or functions. +// `unsafe` features or functions, except to stand in for std APIs that are still +// unstable on WASI Preview 2 (see `wasip2_ffi`). // Due to a suboptimal design aspect of OsStr/OsString on windows, we need to // encode/decode to wide chars on windows operating system. // This prevents borrowing from OsStr on windows. Anyway, if optimally used,# @@ -23,17 +26,22 @@ use std::{borrow::Cow, ffi::OsStr}; #[cfg(all(target_os = "wasi", not(target_env = "p1")))] use wasip2_ffi::{OsStrExt, OsStringExt}; -// WASI Preview 2 uses stable encoded-byte APIs because `std::os::wasi::ffi` is unstable. -// Component-model strings are UTF-8, so decoding through `str` preserves host values. +// `std::os::wasi::ffi` is still unstable on WASI Preview 2, so provide the same traits here. +// On WASI an `OsStr` is a plain byte string, so these conversions keep every byte. #[cfg(all(target_os = "wasi", not(target_env = "p1")))] mod wasip2_ffi { use std::ffi::{OsStr, OsString}; pub trait OsStrExt { + fn from_bytes(bytes: &[u8]) -> &Self; fn as_bytes(&self) -> &[u8]; } impl OsStrExt for OsStr { + fn from_bytes(bytes: &[u8]) -> &Self { + unsafe { Self::from_encoded_bytes_unchecked(bytes) } + } + fn as_bytes(&self) -> &[u8] { self.as_encoded_bytes() } @@ -46,11 +54,7 @@ mod wasip2_ffi { 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(), - } + unsafe { Self::from_encoded_bytes_unchecked(vec) } } fn into_vec(self) -> Vec { @@ -191,20 +195,13 @@ pub fn from_native_int_representation(input: Cow<'_, NativeIntStr>) -> Cow<'_, O Cow::Owned(OsString::from_wide(&input)) } - #[cfg(any(unix, all(target_os = "wasi", target_env = "p1")))] + #[cfg(not(windows))] { 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 @@ -379,8 +376,11 @@ mod tests { } #[test] - fn wasip2_invalid_encoded_bytes_are_lossy() { - let converted = from_native_int_representation_owned(vec![b'f', b'o', 0x80, b'o']); - assert_eq!(converted, OsString::from("fo\u{fffd}o")); + fn wasip2_non_utf8_bytes_are_kept() { + let bytes = vec![b'f', b'o', 0x80, b'o']; + let converted = from_native_int_representation_owned(bytes.clone()); + assert_eq!(converted.as_encoded_bytes(), bytes.as_slice()); + let borrowed = from_native_int_representation(Cow::Borrowed(&bytes)); + assert_eq!(borrowed.as_encoded_bytes(), bytes.as_slice()); } } diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 7ebab240252..94a08b26412 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -388,7 +388,7 @@ static ARGV: LazyLock> = LazyLock::new(|| std::env::args_os().coll #[cfg(any(test, target_os = "wasi"))] fn with_wasi_argv_fallback(mut argv: Vec) -> Vec { if argv.is_empty() { - argv.push(OsString::from("uu")); + argv.push(OsString::from("coreutils")); } argv } @@ -399,8 +399,7 @@ static ARGV: LazyLock> = LazyLock::new(|| with_wasi_argv_fallback(std::env::args_os().collect())); static UTIL_NAME: LazyLock = LazyLock::new(|| { - // 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. + // `ARGV` may be shorter than the multicall layout assumes (e.g. the WASI fallback). 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")); @@ -754,7 +753,7 @@ mod tests { fn wasi_argv_fallback_prevents_empty_argv() { assert_eq!( with_wasi_argv_fallback(Vec::new()), - vec![OsString::from("uu")] + vec![OsString::from("coreutils")] ); let argv = vec![OsString::from("env"), OsString::from("--version")]; From 49d9a8656cd4281852b600e134e436c632ddcbc3 Mon Sep 17 00:00:00 2001 From: Aditya Salunkhe Date: Wed, 23 Sep 2026 19:59:10 +0530 Subject: [PATCH 09/11] safety comment nits --- src/uu/env/src/native_int_str.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/uu/env/src/native_int_str.rs b/src/uu/env/src/native_int_str.rs index bcedf3a69de..d9cd3bafef4 100644 --- a/src/uu/env/src/native_int_str.rs +++ b/src/uu/env/src/native_int_str.rs @@ -39,6 +39,7 @@ mod wasip2_ffi { impl OsStrExt for OsStr { fn from_bytes(bytes: &[u8]) -> &Self { + // SAFETY: WASI `OsStr` is a byte string; this is `std::os::wasi::ffi::OsStrExt::from_bytes`. unsafe { Self::from_encoded_bytes_unchecked(bytes) } } @@ -54,6 +55,7 @@ mod wasip2_ffi { impl OsStringExt for OsString { fn from_vec(vec: Vec) -> Self { + // SAFETY: as above; this is `std::os::wasi::ffi::OsStringExt::from_vec`. unsafe { Self::from_encoded_bytes_unchecked(vec) } } From 2e54a2b6b673551a42f2b857cd8b7f9b73436a08 Mon Sep 17 00:00:00 2001 From: Aditya Salunkhe Date: Sat, 10 Oct 2026 03:37:54 +0530 Subject: [PATCH 10/11] review fixes: drop the argv clamp, document the non-empty invariant --- src/uucore/src/lib/lib.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index f3ddb66c8e5..97a13916337 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -389,17 +389,15 @@ fn with_wasi_argv_fallback(mut argv: Vec) -> Vec { argv } -// WASI embeddings may omit argv; provide a stable utility-name fallback. +// Always non-empty on WASI (embeddings may omit argv); `UTIL_NAME`/`EXECUTION_PHRASE` rely on it. #[cfg(all(not(windows), target_os = "wasi"))] static ARGV: LazyLock> = LazyLock::new(|| with_wasi_argv_fallback(std::env::args_os().collect())); static UTIL_NAME: LazyLock = LazyLock::new(|| { - // `ARGV` may be shorter than the multicall layout assumes (e.g. the WASI fallback). - let last = ARGV.len().saturating_sub(1); - let base_index = usize::from(get_utility_is_second_arg()).min(last); + let base_index = usize::from(get_utility_is_second_arg()); let is_man = usize::from(ARGV[base_index].eq("manpage")); - let argv_index = (base_index + is_man).min(last); + let argv_index = base_index + is_man; // Strip directory path to show only utility name // (e.g., "mkdir" instead of "./target/debug/mkdir") From b234415ab5925def90513c73fa116f56534775b1 Mon Sep 17 00:00:00 2001 From: Aditya Salunkhe Date: Tue, 13 Oct 2026 03:12:50 +0530 Subject: [PATCH 11/11] uucore: drop the redundant not(windows) from the wasi ARGV cfg --- src/uucore/src/lib/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 97a13916337..08ed8724f2f 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -390,7 +390,7 @@ fn with_wasi_argv_fallback(mut argv: Vec) -> Vec { } // Always non-empty on WASI (embeddings may omit argv); `UTIL_NAME`/`EXECUTION_PHRASE` rely on it. -#[cfg(all(not(windows), target_os = "wasi"))] +#[cfg(target_os = "wasi")] static ARGV: LazyLock> = LazyLock::new(|| with_wasi_argv_fallback(std::env::args_os().collect()));