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
17 changes: 2 additions & 15 deletions src/uucore/src/lib/features/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -790,23 +790,10 @@ pub fn are_hardlinks_or_one_way_symlink_to_same_file(source: &Path, target: &Pat
/// * `path` - A reference to the path to be checked.
#[cfg(any(unix, target_os = "wasi"))]
pub fn path_ends_with_terminator(path: &Path) -> bool {
#[cfg(unix)]
use std::os::unix::prelude::OsStrExt;
#[cfg(all(target_os = "wasi", target_env = "p1"))]
use std::os::wasi::ffi::OsStrExt;

#[cfg(all(target_os = "wasi", target_env = "p2"))]
return path
.as_os_str()
path.as_os_str()
.as_encoded_bytes()
.last()
.is_some_and(|&byte| byte == b'/');
#[cfg(not(all(target_os = "wasi", target_env = "p2")))]
return path
.as_os_str()
.as_bytes()
.last()
.is_some_and(|&byte| byte == b'/');
.is_some_and(|&byte| byte == b'/')
}

#[cfg(windows)]
Expand Down
72 changes: 52 additions & 20 deletions src/uucore/src/lib/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,6 @@ use std::io::{BufRead, BufReader};
use std::iter;
#[cfg(unix)]
use std::os::unix::ffi::{OsStrExt, OsStringExt};
#[cfg(all(target_os = "wasi", target_env = "p1"))]
use std::os::wasi::ffi::{OsStrExt, OsStringExt};
use std::str;
use std::str::Utf8Chunk;
use std::sync::{LazyLock, atomic::Ordering};
Expand Down Expand Up @@ -485,15 +483,19 @@ pub fn os_str_as_bytes_lossy(os_string: &OsStr) -> Cow<'_, [u8]> {
///
/// This always succeeds on unix platforms,
/// and fails on other platforms if the bytes can't be parsed as UTF-8.
#[cfg_attr(
any(unix, all(target_os = "wasi", target_env = "p1")),
expect(clippy::unnecessary_wraps)
)]
#[cfg_attr(any(unix, target_os = "wasi"), expect(clippy::unnecessary_wraps))]
pub fn os_str_from_bytes(bytes: &[u8]) -> error::UResult<Cow<'_, OsStr>> {
#[cfg(any(unix, all(target_os = "wasi", target_env = "p1")))]
#[cfg(unix)]
return Ok(Cow::Borrowed(OsStr::from_bytes(bytes)));

#[cfg(not(any(unix, all(target_os = "wasi", target_env = "p1"))))]
#[cfg(target_os = "wasi")]
return Ok(Cow::Borrowed(
// SAFETY: on WASI `OsStr` is a plain byte string with no encoding
// invariant, so every byte sequence is a valid `OsStr`.
unsafe { OsStr::from_encoded_bytes_unchecked(bytes) },

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.

same for the unsafe

));

#[cfg(not(any(unix, target_os = "wasi")))]
Ok(Cow::Owned(OsString::from(str::from_utf8(bytes).map_err(
|_| error::UUsageError::new(1, "Unable to transform bytes into OsStr"),
)?)))
Expand All @@ -503,15 +505,17 @@ pub fn os_str_from_bytes(bytes: &[u8]) -> error::UResult<Cow<'_, OsStr>> {
///
/// This always succeeds on unix platforms,
/// and fails on other platforms if the bytes can't be parsed as UTF-8.
#[cfg_attr(
any(unix, all(target_os = "wasi", target_env = "p1")),
expect(clippy::unnecessary_wraps)
)]
#[cfg_attr(any(unix, target_os = "wasi"), expect(clippy::unnecessary_wraps))]
pub fn os_string_from_vec(vec: Vec<u8>) -> error::UResult<OsString> {
#[cfg(any(unix, all(target_os = "wasi", target_env = "p1")))]
#[cfg(unix)]
return Ok(OsString::from_vec(vec));

#[cfg(not(any(unix, all(target_os = "wasi", target_env = "p1"))))]
#[cfg(target_os = "wasi")]
// SAFETY: on WASI `OsString` is a plain byte string with no encoding
// invariant, so every byte sequence is a valid `OsString`.
return Ok(unsafe { OsString::from_encoded_bytes_unchecked(vec) });

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.

can we avoid the unsafe ?


#[cfg(not(any(unix, target_os = "wasi")))]
Ok(OsString::from(String::from_utf8(vec).map_err(|_| {
error::UUsageError::new(1, "invalid UTF-8 was detected in one or more arguments")
})?))
Expand All @@ -521,14 +525,13 @@ pub fn os_string_from_vec(vec: Vec<u8>) -> error::UResult<OsString> {
///
/// This always succeeds on unix platforms,
/// and fails on other platforms if the bytes can't be parsed as UTF-8.
#[cfg_attr(
any(unix, all(target_os = "wasi", target_env = "p1")),
expect(clippy::unnecessary_wraps)
)]
#[cfg_attr(any(unix, target_os = "wasi"), expect(clippy::unnecessary_wraps))]
pub fn os_string_to_vec(s: OsString) -> error::UResult<Vec<u8>> {
#[cfg(any(unix, all(target_os = "wasi", target_env = "p1")))]
#[cfg(unix)]
let v = s.into_vec();
#[cfg(not(any(unix, all(target_os = "wasi", target_env = "p1"))))]
#[cfg(target_os = "wasi")]
let v = s.into_encoded_bytes();
#[cfg(not(any(unix, target_os = "wasi")))]
let v = s
.into_string()
.map_err(|_| {
Expand Down Expand Up @@ -760,6 +763,35 @@ mod tests {
test_invalid_utf8_args_ignore(os_str);
}

/// On byte-oriented platforms — unix and every WASI environment — the
/// `OsString` helpers must round-trip arbitrary bytes, including sequences
/// that are not valid UTF-8. This regressed on wasm32-wasip2 and
/// wasm32-wasip3, where the conversions fell back to the UTF-8-validating
/// path and returned an error instead.
#[cfg(any(unix, target_os = "wasi"))]
#[test]
fn os_string_from_vec_roundtrips_non_utf8() {
let source = vec![0x66, 0x6f, 0x80, 0x6f];

let os_string = os_string_from_vec(source.clone()).unwrap();
// The value really does hold bytes that are not valid UTF-8.
assert!(os_string.to_str().is_none());
assert_eq!(os_string_to_vec(os_string).unwrap(), source);
}

/// Same invariant for the borrowed `OsStr` conversions, which must agree
/// with `os_string_from_vec` on what is representable.
#[cfg(any(unix, target_os = "wasi"))]
#[test]
fn os_str_from_bytes_roundtrips_non_utf8() {
let source = [0x66, 0x6f, 0x80, 0x6f];

let os_str = os_str_from_bytes(&source).unwrap();
assert!(os_str.to_str().is_none());
assert_eq!(os_str_as_bytes(&os_str).unwrap(), &source);
assert_eq!(os_str_as_bytes_lossy(&os_str).into_owned(), source);
}

#[test]
fn test_format_usage() {
assert_eq!(format_usage("expr EXPRESSION"), "expr EXPRESSION");
Expand Down
Loading