From e4a169f899b0189583d06c3baf4b57b311f8c793 Mon Sep 17 00:00:00 2001 From: Anthony DePasquale Date: Thu, 28 May 2026 13:32:19 +0200 Subject: [PATCH 1/4] wasi: clean up core platform abstractions --- src/uu/env/src/native_int_str.rs | 4 ++- src/uu/tail/src/platform/mod.rs | 15 ++++++++ src/uucore/src/lib/features/fs.rs | 51 ++++++---------------------- src/uucore/src/lib/features/fsext.rs | 11 ++---- src/uucore/src/lib/mods/io.rs | 9 +---- 5 files changed, 31 insertions(+), 59 deletions(-) diff --git a/src/uu/env/src/native_int_str.rs b/src/uu/env/src/native_int_str.rs index a52686b9dc1..b5a09033233 100644 --- a/src/uu/env/src/native_int_str.rs +++ b/src/uu/env/src/native_int_str.rs @@ -13,8 +13,10 @@ // this conversion needs to be done only once in the beginning and at the end. use std::ffi::OsString; -#[cfg(not(target_os = "windows"))] +#[cfg(unix)] use std::os::unix::ffi::{OsStrExt, OsStringExt}; +#[cfg(target_os = "wasi")] +use std::os::wasi::ffi::{OsStrExt, OsStringExt}; #[cfg(target_os = "windows")] use std::os::windows::prelude::*; use std::{borrow::Cow, ffi::OsStr}; diff --git a/src/uu/tail/src/platform/mod.rs b/src/uu/tail/src/platform/mod.rs index bb77501fdcb..8a59a5cdc93 100644 --- a/src/uu/tail/src/platform/mod.rs +++ b/src/uu/tail/src/platform/mod.rs @@ -18,6 +18,21 @@ pub use self::windows::{Pid, ProcessChecker, supports_pid_checks}; #[cfg(target_os = "wasi")] pub type Pid = u64; +#[cfg(target_os = "wasi")] +#[allow(dead_code)] +pub struct ProcessChecker; + +#[cfg(target_os = "wasi")] +#[allow(dead_code)] +impl ProcessChecker { + pub fn new(_pid: Pid) -> Self { + Self + } + pub fn is_dead(&self) -> bool { + true + } +} + #[cfg(target_os = "wasi")] pub fn supports_pid_checks(_pid: Pid) -> bool { false diff --git a/src/uucore/src/lib/features/fs.rs b/src/uucore/src/lib/features/fs.rs index aece06bd604..a31d30e00b0 100644 --- a/src/uucore/src/lib/features/fs.rs +++ b/src/uucore/src/lib/features/fs.rs @@ -18,7 +18,7 @@ use std::fs::read_dir; use std::hash::Hash; use std::io::Stdin; use std::io::{Error, ErrorKind, Result as IOResult}; -#[cfg(unix)] +#[cfg(any(unix, target_os = "wasi"))] use std::os::fd::AsFd; #[cfg(unix)] use std::os::unix::fs::MetadataExt; @@ -39,15 +39,13 @@ macro_rules! has { /// Information to uniquely identify a file pub struct FileInformation( - #[cfg(unix)] rustix::fs::Stat, + #[cfg(any(unix, target_os = "wasi"))] rustix::fs::Stat, #[cfg(windows)] winapi_util::file::Information, - // WASI does not have nix::sys::stat, so we store std::fs::Metadata instead. - #[cfg(target_os = "wasi")] fs::Metadata, ); impl FileInformation { /// Get information from a currently open file - #[cfg(unix)] + #[cfg(any(unix, target_os = "wasi"))] pub fn from_file(file: &impl AsFd) -> IOResult { let stat = rustix::fs::fstat(file)?; Ok(Self(stat)) @@ -65,7 +63,7 @@ impl FileInformation { /// If `path` points to a symlink and `dereference` is true, information about /// the link's target will be returned. pub fn from_path(path: impl AsRef, dereference: bool) -> IOResult { - #[cfg(unix)] + #[cfg(any(unix, target_os = "wasi"))] { let stat = if dereference { rustix::fs::stat(path.as_ref()) @@ -89,20 +87,10 @@ impl FileInformation { let file = open_options.read(true).open(path.as_ref())?; Self::from_file(&file) } - // WASI: use std::fs::metadata / symlink_metadata since nix is not available - #[cfg(target_os = "wasi")] - { - let metadata = if dereference { - fs::metadata(path.as_ref()) - } else { - fs::symlink_metadata(path.as_ref()) - }; - Ok(Self(metadata?)) - } } pub fn file_size(&self) -> u64 { - #[cfg(unix)] + #[cfg(any(unix, target_os = "wasi"))] { assert!(self.0.st_size >= 0, "File size is negative"); self.0.st_size.try_into().unwrap() @@ -111,10 +99,6 @@ impl FileInformation { { self.0.file_size() } - #[cfg(target_os = "wasi")] - { - self.0.len() - } } #[cfg(windows)] @@ -141,6 +125,8 @@ impl FileInformation { target_pointer_width = "64" ))] return self.0.st_nlink; + #[cfg(target_os = "wasi")] + return self.0.st_nlink; #[cfg(all( unix, any( @@ -165,12 +151,9 @@ impl FileInformation { return self.0.st_nlink.try_into().unwrap(); #[cfg(windows)] return self.0.number_of_links(); - // WASI: nlink is not available in std::fs::Metadata, return 1 - #[cfg(target_os = "wasi")] - return 1; } - #[cfg(unix)] + #[cfg(any(unix, target_os = "wasi"))] pub fn inode(&self) -> u64 { #[cfg(all(not(any(target_os = "netbsd")), target_pointer_width = "64"))] return self.0.st_ino; @@ -180,22 +163,13 @@ impl FileInformation { } } -#[cfg(unix)] +#[cfg(any(unix, target_os = "wasi"))] impl PartialEq for FileInformation { fn eq(&self, other: &Self) -> bool { self.0.st_dev == other.0.st_dev && self.0.st_ino == other.0.st_ino } } -// WASI: compare by file type and size as a basic heuristic since -// device/inode numbers are not available through std::fs::Metadata. -#[cfg(target_os = "wasi")] -impl PartialEq for FileInformation { - fn eq(&self, other: &Self) -> bool { - self.0.file_type() == other.0.file_type() && self.0.len() == other.0.len() - } -} - #[cfg(target_os = "windows")] impl PartialEq for FileInformation { fn eq(&self, other: &Self) -> bool { @@ -208,7 +182,7 @@ impl Eq for FileInformation {} impl Hash for FileInformation { fn hash(&self, state: &mut H) { - #[cfg(unix)] + #[cfg(any(unix, target_os = "wasi"))] { self.0.st_dev.hash(state); self.0.st_ino.hash(state); @@ -218,11 +192,6 @@ impl Hash for FileInformation { self.0.volume_serial_number().hash(state); self.0.file_index().hash(state); } - #[cfg(target_os = "wasi")] - { - self.0.len().hash(state); - self.0.file_type().is_dir().hash(state); - } } } diff --git a/src/uucore/src/lib/features/fsext.rs b/src/uucore/src/lib/features/fsext.rs index 0ede0f2875f..7f2de568c42 100644 --- a/src/uucore/src/lib/features/fsext.rs +++ b/src/uucore/src/lib/features/fsext.rs @@ -428,7 +428,6 @@ fn mount_dev_id(mount_dir: &OsStr) -> String { } } -#[cfg(not(target_os = "wasi"))] use crate::error::UResult; #[cfg(any( target_os = "freebsd", @@ -459,7 +458,7 @@ use std::ptr; use std::slice; /// Read file system list. -#[cfg(not(target_os = "wasi"))] +#[cfg_attr(target_os = "wasi", allow(clippy::unnecessary_wraps))] pub fn read_fs_list() -> UResult> { #[cfg(any(target_os = "linux", target_os = "android", target_os = "cygwin"))] { @@ -540,6 +539,7 @@ pub fn read_fs_list() -> UResult> { target_os = "redox", target_os = "illumos", target_os = "solaris", + target_os = "wasi" ))] { // No method to read mounts on these platforms @@ -547,13 +547,6 @@ pub fn read_fs_list() -> UResult> { } } -/// Read file system list. -#[cfg(target_os = "wasi")] -pub fn read_fs_list() -> Vec { - // No method to read mounts on WASI - Vec::new() -} - #[derive(Debug, Clone)] pub struct FsUsage { pub blocksize: u64, diff --git a/src/uucore/src/lib/mods/io.rs b/src/uucore/src/lib/mods/io.rs index eccbd10e37a..67e4df9e3ed 100644 --- a/src/uucore/src/lib/mods/io.rs +++ b/src/uucore/src/lib/mods/io.rs @@ -99,14 +99,7 @@ impl OwnedFileDescriptorOrHandle { /// instantiates a corresponding `Stdio` #[cfg(not(target_os = "wasi"))] pub fn into_stdio(self) -> Stdio { - #[cfg(not(target_os = "wasi"))] - { - Stdio::from(self.fx) - } - #[cfg(target_os = "wasi")] - { - Stdio::from(File::from(self.fx)) - } + Stdio::from(self.fx) } /// WASI: Stdio::from(OwnedFd) is not available, convert via File instead. From 394618d63e3f8a7d6e605e06848b8f6a596dd64b Mon Sep 17 00:00:00 2001 From: Anthony DePasquale Date: Thu, 28 May 2026 14:24:03 +0200 Subject: [PATCH 2/4] tests: update WASI comm order-check expectations --- tests/by-util/test_comm.rs | 63 ++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 37 deletions(-) diff --git a/tests/by-util/test_comm.rs b/tests/by-util/test_comm.rs index d082284485f..f0b7baed291 100644 --- a/tests/by-util/test_comm.rs +++ b/tests/by-util/test_comm.rs @@ -455,17 +455,12 @@ fn test_sorted() { let at = &scene.fixtures; at.write("comm1", "1\n3"); at.write("comm2", "3\n2"); - let cmd = scene.ucmd().args(&["comm1", "comm2"]).run(); - // WASI's strcoll (C locale only) may not detect unsorted input, - // but the comparison output is still correct. - if std::env::var("UUTESTS_WASM_RUNNER").is_ok() { - cmd.success().stdout_is("1\n\t\t3\n\t2\n"); - } else { - cmd.failure() - .code_is(1) - .stdout_is("1\n\t\t3\n\t2\n") - .stderr_is("comm: file 2 is not in sorted order\ncomm: input is not in sorted order\n"); - } + scene + .ucmd() + .args(&["comm1", "comm2"]) + .fails_with_code(1) + .stdout_is("1\n\t\t3\n\t2\n") + .stderr_is("comm: file 2 is not in sorted order\ncomm: input is not in sorted order\n"); } #[test] @@ -492,19 +487,16 @@ fn test_both_inputs_out_of_order() { at.write("file_a", "3\n1\n0\n"); at.write("file_b", "3\n2\n0\n"); - let cmd = scene.ucmd().args(&["file_a", "file_b"]).run(); - if std::env::var("UUTESTS_WASM_RUNNER").is_ok() { - cmd.success().stdout_is("\t\t3\n1\n0\n\t2\n\t0\n"); - } else { - cmd.failure() - .code_is(1) - .stdout_is("\t\t3\n1\n0\n\t2\n\t0\n") - .stderr_is( - "comm: file 1 is not in sorted order\n\ - comm: file 2 is not in sorted order\n\ - comm: input is not in sorted order\n", - ); - } + scene + .ucmd() + .args(&["file_a", "file_b"]) + .fails_with_code(1) + .stdout_is("\t\t3\n1\n0\n\t2\n\t0\n") + .stderr_is( + "comm: file 1 is not in sorted order\n\ + comm: file 2 is not in sorted order\n\ + comm: input is not in sorted order\n", + ); } #[test] @@ -514,19 +506,16 @@ fn test_both_inputs_out_of_order_last_pair() { at.write("file_a", "3\n1\n"); at.write("file_b", "3\n2\n"); - let cmd = scene.ucmd().args(&["file_a", "file_b"]).run(); - if std::env::var("UUTESTS_WASM_RUNNER").is_ok() { - cmd.success().stdout_is("\t\t3\n1\n\t2\n"); - } else { - cmd.failure() - .code_is(1) - .stdout_is("\t\t3\n1\n\t2\n") - .stderr_is( - "comm: file 1 is not in sorted order\n\ - comm: file 2 is not in sorted order\n\ - comm: input is not in sorted order\n", - ); - } + scene + .ucmd() + .args(&["file_a", "file_b"]) + .fails_with_code(1) + .stdout_is("\t\t3\n1\n\t2\n") + .stderr_is( + "comm: file 1 is not in sorted order\n\ + comm: file 2 is not in sorted order\n\ + comm: input is not in sorted order\n", + ); } #[test] From cbcbd781f77d2ea2c401acce35c568409cd979d8 Mon Sep 17 00:00:00 2001 From: Anthony DePasquale Date: Thu, 28 May 2026 17:00:03 +0200 Subject: [PATCH 3/4] wasi: remove ProcessChecker --- src/uu/tail/src/platform/mod.rs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/uu/tail/src/platform/mod.rs b/src/uu/tail/src/platform/mod.rs index 8a59a5cdc93..bb77501fdcb 100644 --- a/src/uu/tail/src/platform/mod.rs +++ b/src/uu/tail/src/platform/mod.rs @@ -18,21 +18,6 @@ pub use self::windows::{Pid, ProcessChecker, supports_pid_checks}; #[cfg(target_os = "wasi")] pub type Pid = u64; -#[cfg(target_os = "wasi")] -#[allow(dead_code)] -pub struct ProcessChecker; - -#[cfg(target_os = "wasi")] -#[allow(dead_code)] -impl ProcessChecker { - pub fn new(_pid: Pid) -> Self { - Self - } - pub fn is_dead(&self) -> bool { - true - } -} - #[cfg(target_os = "wasi")] pub fn supports_pid_checks(_pid: Pid) -> bool { false From 5bcd0ad0390ff273ab87739952e4925c81877d06 Mon Sep 17 00:00:00 2001 From: Anthony DePasquale Date: Thu, 28 May 2026 21:08:21 +0200 Subject: [PATCH 4/4] Fix failing OpenBSD test The command fails during argument validation before reading stdin, so remove unnecessary piped input that could hit EPIPE. --- tests/by-util/test_tr.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/by-util/test_tr.rs b/tests/by-util/test_tr.rs index bfb490a56a5..20849d66cc5 100644 --- a/tests/by-util/test_tr.rs +++ b/tests/by-util/test_tr.rs @@ -385,7 +385,6 @@ fn test_truncate_with_set1_shorter_than_set2() { fn test_truncate_applies_before_complement_with_class() { new_ucmd!() .args(&["-ct", "[:digit:]", "X"]) - .pipe_in("A") .fails() .stderr_contains("when translating with complemented character classes,\nstring2 must map all characters in the domain to one"); }