From fe299ed39791ebbc7e10d94a778480ea2af2e801 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 30 Mar 2026 11:36:06 +0200 Subject: [PATCH 1/3] uucore: add WASI support for FileInformation, fs, and io modules Add #[cfg(target_os = "wasi")] branches to support building uucore for the wasm32-wasip1 target: - FileInformation: use std::fs::Metadata instead of nix::sys::stat - is_stdin_directory: return false (stdin is never a directory on WASI) - sane_blksize: use default block size (no MetadataExt on WASI) - read_fs_list: return empty list (no mount info on WASI) - into_stdio: convert via File since Stdio::from(OwnedFd) is unavailable --- src/uucore/src/lib/features/fs.rs | 45 ++++++++++++++++++++++++++-- src/uucore/src/lib/features/fsext.rs | 5 ++-- src/uucore/src/lib/mods/io.rs | 7 +++++ 3 files changed, 52 insertions(+), 5 deletions(-) diff --git a/src/uucore/src/lib/features/fs.rs b/src/uucore/src/lib/features/fs.rs index 6ef229d175c..bc7f92dd78b 100644 --- a/src/uucore/src/lib/features/fs.rs +++ b/src/uucore/src/lib/features/fs.rs @@ -45,6 +45,8 @@ macro_rules! has { pub struct FileInformation( #[cfg(unix)] nix::sys::stat::FileStat, #[cfg(windows)] winapi_util::file::Information, + // WASI does not have nix::sys::stat, so we store std::fs::Metadata instead. + #[cfg(target_os = "wasi")] std::fs::Metadata, ); impl FileInformation { @@ -91,6 +93,16 @@ 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 { + std::fs::metadata(path.as_ref()) + } else { + std::fs::symlink_metadata(path.as_ref()) + }; + Ok(Self(metadata?)) + } } pub fn file_size(&self) -> u64 { @@ -103,6 +115,10 @@ impl FileInformation { { self.0.file_size() } + #[cfg(target_os = "wasi")] + { + self.0.len() + } } #[cfg(windows)] @@ -153,6 +169,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)] @@ -172,6 +191,15 @@ impl PartialEq for FileInformation { } } +// 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 { @@ -194,6 +222,10 @@ 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); + } } } @@ -778,11 +810,18 @@ pub fn is_stdin_directory(stdin: &Stdin) -> bool { } false } + + // WASI: stdin is never a directory + #[cfg(target_os = "wasi")] + { + let _ = stdin; + false + } } pub mod sane_blksize { - #[cfg(not(target_os = "windows"))] + #[cfg(unix)] use std::os::unix::fs::MetadataExt; use std::{fs::metadata, path::Path}; @@ -809,12 +848,12 @@ pub mod sane_blksize { #[cfg(unix)] metadata: &std::fs::Metadata, #[cfg(not(unix))] _: &std::fs::Metadata, ) -> u64 { - #[cfg(not(target_os = "windows"))] + #[cfg(unix)] { sane_blksize(metadata.blksize()) } - #[cfg(target_os = "windows")] + #[cfg(not(unix))] { DEFAULT } diff --git a/src/uucore/src/lib/features/fsext.rs b/src/uucore/src/lib/features/fsext.rs index 5eacc7a4c49..26070b108de 100644 --- a/src/uucore/src/lib/features/fsext.rs +++ b/src/uucore/src/lib/features/fsext.rs @@ -535,10 +535,11 @@ pub fn read_fs_list() -> UResult> { target_os = "aix", target_os = "redox", target_os = "illumos", - target_os = "solaris" + target_os = "solaris", + target_os = "wasi" ))] { - // No method to read mounts, yet + // No method to read mounts on these platforms Ok(Vec::new()) } } diff --git a/src/uucore/src/lib/mods/io.rs b/src/uucore/src/lib/mods/io.rs index c83c3d98744..a9e4a7bcbec 100644 --- a/src/uucore/src/lib/mods/io.rs +++ b/src/uucore/src/lib/mods/io.rs @@ -73,10 +73,17 @@ impl OwnedFileDescriptorOrHandle { } /// instantiates a corresponding `Stdio` + #[cfg(not(target_os = "wasi"))] pub fn into_stdio(self) -> Stdio { Stdio::from(self.fx) } + /// WASI: Stdio::from(OwnedFd) is not available, convert via File instead. + #[cfg(target_os = "wasi")] + pub fn into_stdio(self) -> Stdio { + Stdio::from(File::from(self.fx)) + } + /// clones self. useful when needing another /// owned reference to same file pub fn try_clone(&self) -> io::Result { From 711290f767de162af52f620ff1b0d40dc67fac5d Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 30 Mar 2026 11:37:24 +0200 Subject: [PATCH 2/3] ls: make hostname detection return nothing on WASI target The hostname crate does not support WASI (no OS-level hostname API). --- src/uu/ls/Cargo.toml | 5 ++++- src/uu/ls/src/display.rs | 5 +++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/uu/ls/Cargo.toml b/src/uu/ls/Cargo.toml index a82ad13c397..b8688819ef8 100644 --- a/src/uu/ls/Cargo.toml +++ b/src/uu/ls/Cargo.toml @@ -24,7 +24,6 @@ path = "src/ls.rs" ansi-width = { workspace = true } clap = { workspace = true, features = ["env"] } glob = { workspace = true } -hostname = { workspace = true } lscolors = { workspace = true } rustc-hash = { workspace = true } selinux = { workspace = true, optional = true } @@ -46,6 +45,10 @@ uucore = { workspace = true, features = [ uutils_term_grid = { workspace = true } fluent = { workspace = true } +# hostname crate does not support WASI (no OS-level hostname API) +[target.'cfg(not(target_os = "wasi"))'.dependencies] +hostname = { workspace = true } + [[bin]] name = "ls" path = "src/main.rs" diff --git a/src/uu/ls/src/display.rs b/src/uu/ls/src/display.rs index 929ce7e4610..d1c35e45c7a 100644 --- a/src/uu/ls/src/display.rs +++ b/src/uu/ls/src/display.rs @@ -1146,7 +1146,12 @@ fn classify_file(path: &PathData) -> Option { } fn create_hyperlink(name: &OsStr, path: &PathData) -> OsString { + // The `hostname` crate does not support WASI (no OS-level hostname API), + // so we use an empty string for hyperlinks on WASI. + #[cfg(not(target_os = "wasi"))] static HOSTNAME: LazyLock = LazyLock::new(|| hostname::get().unwrap_or_default()); + #[cfg(target_os = "wasi")] + static HOSTNAME: LazyLock = LazyLock::new(OsString::new); // OSC 8 hyperlink format: \x1b]8;;URL\x1b\\TEXT\x1b]8;;\x1b\\ // \x1b = ESC, \x1b\\ = ESC backslash From 8f9e72eaab3a31ab47141bdc1bc616ac25b5a373 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 30 Mar 2026 11:39:58 +0200 Subject: [PATCH 3/3] feat_wasm: add ls to the WASM-compatible utilities Now that hostname is optional and uucore has WASI stubs, ls can be built for the wasm32-wasip1 target. --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 4f98541268e..9cdfab875e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -185,6 +185,7 @@ feat_wasm = [ "base64", "basenc", "cut", + "ls", "date", "dircolors", "dirname",