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
4 changes: 3 additions & 1 deletion .github/workflows/wasi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,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
Expand Down Expand Up @@ -78,3 +78,5 @@ jobs:
test_sha512sum:: test_shuf:: test_sleep:: test_sum:: test_tail:: test_tee:: \
test_test:: test_touch:: test_tr:: test_true:: test_truncate:: test_uname:: \
test_uniq:: test_unexpand:: test_unlink:: test_vdir:: test_wc:: test_yes::
- name: Check uucore fs feature builds standalone
run: cargo check --target ${{ matrix.job.target }} --no-default-features -p uucore --features fs
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,7 @@ feat_common_core = [
"dircolors",
"dirname",
"echo",
"env",
"expand",
"expr",
"factor",
Expand Down Expand Up @@ -577,7 +578,6 @@ feat_Tier1 = [
"feat_common_core",
"df",
"du",
"env",
"hostname",
"kill",
"nohup",
Expand Down
78 changes: 76 additions & 2 deletions src/uu/env/src/native_int_str.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,#
Expand All @@ -15,11 +18,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};
Comment thread
Copilot marked this conversation as resolved.
#[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};

// `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")))]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uucore's os_str_from_bytes/os_string_from_vec still reject non-UTF-8 on p2, so env and uucore now disagree.
could this shim live in uucore so both use it? can be done in a different PR

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thats a fair point, I can migrate that in a follow-up PR very soon (today?)

mod wasip2_ffi {
Comment thread
Copilot marked this conversation as resolved.
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 {
// SAFETY: WASI `OsStr` is a byte string; this is `std::os::wasi::ffi::OsStrExt::from_bytes`.
unsafe { Self::from_encoded_bytes_unchecked(bytes) }
}

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 {
// SAFETY: as above; this is `std::os::wasi::ffi::OsStringExt::from_vec`.
unsafe { Self::from_encoded_bytes_unchecked(vec) }
}

fn into_vec(self) -> Vec<u8> {
self.into_encoded_bytes()
}
}
}

#[cfg(not(windows))]
use u8 as NativeIntCharU;
Expand Down Expand Up @@ -312,3 +356,33 @@ 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_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());
}
}
26 changes: 25 additions & 1 deletion src/uucore/src/lib/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -378,9 +378,22 @@ 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());

#[cfg(any(test, target_os = "wasi"))]
fn with_wasi_argv_fallback(mut argv: Vec<OsString>) -> Vec<OsString> {
if argv.is_empty() {
argv.push(OsString::from("coreutils"));
}
argv
}

// Always non-empty on WASI (embeddings may omit argv); `UTIL_NAME`/`EXECUTION_PHRASE` rely on it.
#[cfg(target_os = "wasi")]
static ARGV: LazyLock<Vec<OsString>> =
LazyLock::new(|| with_wasi_argv_fallback(std::env::args_os().collect()));

static UTIL_NAME: LazyLock<String> = LazyLock::new(|| {
let base_index = usize::from(get_utility_is_second_arg());
let is_man = usize::from(ARGV[base_index].eq("manpage"));
Comment thread
Copilot marked this conversation as resolved.
Expand Down Expand Up @@ -730,6 +743,17 @@ mod tests {
]
}

#[test]
fn wasi_argv_fallback_prevents_empty_argv() {
assert_eq!(
with_wasi_argv_fallback(Vec::new()),
vec![OsString::from("coreutils")]
);

let argv = vec![OsString::from("env"), OsString::from("--version")];
assert_eq!(with_wasi_argv_fallback(argv.clone()), argv);
}

#[cfg(unix)]
fn test_invalid_utf8_args_lossy(os_str: &OsStr) {
// assert our string is invalid utf8
Expand Down
Loading