From c728f1b6913d10661d979d48e10b7e14402854f7 Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Fri, 3 Jul 2026 20:34:01 -0700 Subject: [PATCH] fix(registry): coreutils stat chmod ls show real permission bits --- .../brush-interactive/0001-wasi-support.patch | 16 ---- .../crates/uu_chmod/0001-wasi-compat.patch | 89 +++++++++++++++---- .../0001-wasi-host-fs-mode-display.patch | 42 ++++++--- .../uu_stat/0001-wasi-metadata-compat.patch | 49 +++++++--- registry/native/scripts/patch-vendor.sh | 9 +- registry/native/stubs/uucore/build.rs | 19 ++-- 6 files changed, 155 insertions(+), 69 deletions(-) diff --git a/registry/native/patches/crates/brush-interactive/0001-wasi-support.patch b/registry/native/patches/crates/brush-interactive/0001-wasi-support.patch index 3a0be1c35..bd64fe387 100644 --- a/registry/native/patches/crates/brush-interactive/0001-wasi-support.patch +++ b/registry/native/patches/crates/brush-interactive/0001-wasi-support.patch @@ -139,19 +139,3 @@ diff -ruN '--exclude=*.orig' a/src/reedline/validator.rs b/src/reedline/validato let shell = tokio::task::block_in_place(|| { tokio::runtime::Handle::current().block_on(self.shell.lock()) }); ---- a/Cargo.toml -+++ b/Cargo.toml -@@ -91,6 +91,13 @@ - "signal", - ] - -+[target."cfg(target_arch = \"wasm32\")".dependencies.tokio] -+version = "1.48.0" -+features = [ -+ "macros", -+ "sync", -+] -+ - [target."cfg(unix)".dependencies.nix] - version = "0.30.1" - features = ["term"] diff --git a/registry/native/patches/crates/uu_chmod/0001-wasi-compat.patch b/registry/native/patches/crates/uu_chmod/0001-wasi-compat.patch index c8297fe54..8e41964b6 100644 --- a/registry/native/patches/crates/uu_chmod/0001-wasi-compat.patch +++ b/registry/native/patches/crates/uu_chmod/0001-wasi-compat.patch @@ -1,6 +1,6 @@ --- a/src/chmod.rs +++ b/src/chmod.rs -@@ -8,16 +8,70 @@ +@@ -8,16 +8,87 @@ use clap::{Arg, ArgAction, Command}; use std::ffi::OsString; use std::fs; @@ -27,23 +27,42 @@ + mod host_fs { + #[link(wasm_import_module = "host_fs")] + unsafe extern "C" { -+ pub fn chmod(path_ptr: *const u8, path_len: u32, mode: u32) -> u32; ++ pub fn chmod(fd: u32, path_ptr: *const u8, path_len: u32, mode: u32) -> u32; ++ pub fn path_mode( ++ fd: u32, ++ path_ptr: *const u8, ++ path_len: u32, ++ follow_symlinks: u32, ++ ) -> u32; + } + } + -+ /// Extension trait to provide mode() on WASI Metadata. -+ pub trait MetadataMode { -+ fn mode(&self) -> u32; ++ fn fallback_mode(meta: &fs::Metadata) -> u32 { ++ let ft = meta.file_type(); ++ let base = if ft.is_dir() { 0o755 } else { 0o644 }; ++ if meta.permissions().readonly() { ++ base & !0o222 ++ } else { ++ base ++ } + } -+ impl MetadataMode for fs::Metadata { -+ fn mode(&self) -> u32 { -+ let ft = self.file_type(); -+ let base = if ft.is_dir() { 0o755 } else { 0o644 }; -+ if self.permissions().readonly() { -+ base & !0o222 -+ } else { -+ base -+ } ++ ++ pub fn mode_for_path(path: &Path, meta: &fs::Metadata, follow_symlinks: bool) -> u32 { ++ let Some(path_str) = path.to_str() else { ++ return fallback_mode(meta); ++ }; ++ let mode = unsafe { ++ host_fs::path_mode( ++ 3, ++ path_str.as_ptr(), ++ path_str.len() as u32, ++ if follow_symlinks { 1 } else { 0 }, ++ ) ++ }; ++ if mode == 0 { ++ fallback_mode(meta) ++ } else { ++ mode & 0o7777 + } + } + @@ -55,7 +74,7 @@ + .to_str() + .ok_or_else(|| Error::new(ErrorKind::InvalidInput, "path is not valid UTF-8"))? + .as_bytes(); -+ let status = unsafe { host_fs::chmod(bytes.as_ptr(), bytes.len() as u32, mode) }; ++ let status = unsafe { host_fs::chmod(3, bytes.as_ptr(), bytes.len() as u32, mode) }; + if status == 0 { + return Ok(()); + } @@ -65,13 +84,47 @@ + fs::set_permissions(path, perms) + } +} -+#[cfg(target_os = "wasi")] -+use wasi_compat::MetadataMode; + #[cfg(all(unix, not(target_os = "redox")))] use uucore::safe_traversal::{DirFd, SymlinkBehavior}; use uucore::{format_usage, show, show_error}; -@@ -683,7 +737,12 @@ +@@ -121,7 +192,16 @@ + let preserve_root = matches.get_flag(options::PRESERVE_ROOT); + let fmode = match matches.get_one::(options::REFERENCE) { + Some(fref) => match fs::metadata(fref) { +- Ok(meta) => Some(meta.mode() & 0o7777), ++ Ok(meta) => { ++ #[cfg(target_os = "wasi")] ++ { ++ Some(wasi_compat::mode_for_path(Path::new(fref), &meta, true)) ++ } ++ #[cfg(not(target_os = "wasi"))] ++ { ++ Some(meta.mode() & 0o7777) ++ } ++ } + Err(_) => { + return Err(ChmodError::CannotStat(fref.into()).into()); + } +@@ -623,7 +703,16 @@ + let metadata = get_metadata(file, dereference); + + let fperm = match metadata { +- Ok(meta) => meta.mode() & 0o7777, ++ Ok(meta) => { ++ #[cfg(target_os = "wasi")] ++ { ++ wasi_compat::mode_for_path(file, &meta, dereference) ++ } ++ #[cfg(not(target_os = "wasi"))] ++ { ++ meta.mode() & 0o7777 ++ } ++ } + Err(err) => { + // Handle dangling symlinks or other errors + return if file.is_symlink() && !dereference { +@@ -683,7 +772,12 @@ // Use the helper method for consistent reporting self.report_permission_change(file, fperm, mode); Ok(()) diff --git a/registry/native/patches/crates/uu_ls/0001-wasi-host-fs-mode-display.patch b/registry/native/patches/crates/uu_ls/0001-wasi-host-fs-mode-display.patch index 460cf4f41..ff68f349e 100644 --- a/registry/native/patches/crates/uu_ls/0001-wasi-host-fs-mode-display.patch +++ b/registry/native/patches/crates/uu_ls/0001-wasi-host-fs-mode-display.patch @@ -9,7 +9,7 @@ fsext::{MetadataTimeField, metadata_get_time}, line_ending::LineEnding, os_str_as_bytes_lossy, -@@ -77,6 +77,38 @@ +@@ -77,6 +77,60 @@ translate, version_cmp::version_cmp, }; @@ -18,37 +18,59 @@ + +#[cfg(target_os = "wasi")] +mod wasi_host_fs { ++ use std::env; ++ + use super::{Metadata, Path}; + + mod host_fs { + #[link(wasm_import_module = "host_fs")] + unsafe extern "C" { -+ pub fn path_mode(path_ptr: *const u8, path_len: u32, follow_symlinks: u32) -> u32; ++ pub fn path_mode( ++ fd: u32, ++ path_ptr: *const u8, ++ path_len: u32, ++ follow_symlinks: u32, ++ ) -> u32; + } + } + -+ pub fn mode_for_path(path: &Path, metadata: &Metadata, follow_symlinks: bool) -> u32 { ++ fn fallback_mode(metadata: &Metadata) -> u32 { ++ if metadata.is_dir() { 0o040755 } else { 0o100644 } ++ } ++ ++ fn raw_mode_for_path(path: &Path, follow_symlinks: bool) -> u32 { + let Some(path_str) = path.to_str() else { -+ return if metadata.is_dir() { 0o040755 } else { 0o100644 }; ++ return 0; + }; -+ let mode = unsafe { ++ unsafe { + host_fs::path_mode( ++ 3, + path_str.as_ptr(), + path_str.len() as u32, + if follow_symlinks { 1 } else { 0 }, + ) -+ }; -+ if mode == 0 { -+ if metadata.is_dir() { 0o040755 } else { 0o100644 } -+ } else { ++ } ++ } ++ ++ pub fn mode_for_path(path: &Path, metadata: &Metadata, follow_symlinks: bool) -> u32 { ++ let mode = raw_mode_for_path(path, follow_symlinks); ++ if mode != 0 { + mode ++ } else if path.is_relative() { ++ env::current_dir() ++ .ok() ++ .map(|cwd| raw_mode_for_path(&cwd.join(path), follow_symlinks)) ++ .filter(|mode| *mode != 0) ++ .unwrap_or_else(|| fallback_mode(metadata)) ++ } else { ++ fallback_mode(metadata) + } + } +} mod dired; use dired::{DiredOutput, is_dired_arg_present}; -@@ -2982,7 +3014,15 @@ +@@ -2982,7 +3036,15 @@ let is_acl_set = false; #[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))] let is_acl_set = has_acl(item.path()); diff --git a/registry/native/patches/crates/uu_stat/0001-wasi-metadata-compat.patch b/registry/native/patches/crates/uu_stat/0001-wasi-metadata-compat.patch index 4aa333abb..fc4a10e43 100644 --- a/registry/native/patches/crates/uu_stat/0001-wasi-metadata-compat.patch +++ b/registry/native/patches/crates/uu_stat/0001-wasi-metadata-compat.patch @@ -25,7 +25,7 @@ use uucore::{entries, format_usage, show_error, show_warning}; use clap::{Arg, ArgAction, ArgMatches, Command}; -@@ -23,10 +29,79 @@ +@@ -23,10 +29,85 @@ use std::ffi::{OsStr, OsString}; use std::fs::{FileType, Metadata}; use std::io::Write; @@ -79,7 +79,12 @@ + mod host_fs { + #[link(wasm_import_module = "host_fs")] + unsafe extern "C" { -+ pub fn path_mode(path_ptr: *const u8, path_len: u32, follow_symlinks: u32) -> u32; ++ pub fn path_mode( ++ fd: u32, ++ path_ptr: *const u8, ++ path_len: u32, ++ follow_symlinks: u32, ++ ) -> u32; + } + } + @@ -89,6 +94,7 @@ + }; + let mode = unsafe { + host_fs::path_mode( ++ 3, + path_str.as_ptr(), + path_str.len() as u32, + if follow_symlinks { 1 } else { 0 }, @@ -105,15 +111,16 @@ use thiserror::Error; use uucore::time::{FormatSystemTimeFallback, format_system_time, system_time_to_sec}; -@@ -1048,11 +1123,16 @@ - precision, - format, +@@ -1032,6 +1113,7 @@ + display_name: &str, + file: &OsString, + file_type: FileType, ++ effective_mode: u32, + from_user: bool, + #[cfg(all(feature = "selinux", any(target_os = "linux", target_os = "android")))] + follow_symbolic_links: bool, +@@ -1050,9 +1132,9 @@ } => { -+ #[cfg(target_os = "wasi")] -+ let effective_mode = -+ wasi_host_fs::mode_for_path(Path::new(file), meta, self.follow); -+ #[cfg(not(target_os = "wasi"))] -+ let effective_mode = meta.mode(); let output = match format { // access rights in octal - 'a' => OutputType::UnsignedOct(0o7777 & meta.mode()), @@ -124,7 +131,7 @@ // number of blocks allocated (see %B) 'b' => OutputType::Unsigned(meta.blocks()), -@@ -1096,9 +1176,9 @@ +@@ -1096,9 +1178,9 @@ // device number in hex 'D' => OutputType::UnsignedHex(meta.dev()), // raw mode in hex @@ -136,3 +143,23 @@ // group ID of owner 'g' => OutputType::Unsigned(meta.gid() as u64), // group name of owner +@@ -1234,6 +1316,11 @@ + match result { + Ok(meta) => { + let file_type = meta.file_type(); ++ #[cfg(target_os = "wasi")] ++ let effective_mode = ++ wasi_host_fs::mode_for_path(Path::new(&file), &meta, follow_symbolic_links); ++ #[cfg(not(target_os = "wasi"))] ++ let effective_mode = meta.mode(); + let tokens = if self.from_user + || !(file_type.is_char_device() || file_type.is_block_device()) + { +@@ -1249,6 +1336,7 @@ + &display_name, + &file, + file_type, ++ effective_mode, + self.from_user, + follow_symbolic_links, + ) { diff --git a/registry/native/scripts/patch-vendor.sh b/registry/native/scripts/patch-vendor.sh index 1c0f3c819..7a9b4f2aa 100755 --- a/registry/native/scripts/patch-vendor.sh +++ b/registry/native/scripts/patch-vendor.sh @@ -113,14 +113,15 @@ for CRATE_DIR in $CRATE_DIRS; do patch -p1 -d "$VENDOR_CRATE" < "$PATCH" > /dev/null 2>&1 echo "applied" elif patch --dry-run -R -p1 -d "$VENDOR_CRATE" < "$PATCH" > /dev/null 2>&1; then - patch -R -p1 -d "$VENDOR_CRATE" < "$PATCH" > /dev/null 2>&1 - patch -p1 -d "$VENDOR_CRATE" < "$PATCH" > /dev/null 2>&1 - echo "reapplied" + echo "already applied" else # Mixed state (e.g. an interrupted earlier run left some # hunks applied): apply the remaining hunks, tolerating # already-applied ones; fail only on genuine rejects. - OUT=$(patch -p1 -N -r /dev/null -d "$VENDOR_CRATE" < "$PATCH" 2>&1); RC=$? + set +e + OUT=$(patch -p1 -N -r /dev/null -d "$VENDOR_CRATE" < "$PATCH" 2>&1) + RC=$? + set -e if [ $RC -le 1 ] && ! echo "$OUT" | grep -q "FAILED"; then echo "converged (mixed state)" else diff --git a/registry/native/stubs/uucore/build.rs b/registry/native/stubs/uucore/build.rs index f4b36c0ac..d2520f10a 100644 --- a/registry/native/stubs/uucore/build.rs +++ b/registry/native/stubs/uucore/build.rs @@ -252,16 +252,15 @@ fn embed_static_utility_locales( for entry in entries { let file_name = entry.file_name(); if let Some(dir_name) = file_name.to_str() { - // Match uu_- - if let Some((util_part, _)) = dir_name.split_once('-') { - if let Some(util_name) = util_part.strip_prefix("uu_") { - embed_component_locales( - embedded_file, - locales_to_embed, - util_name, - |locale| entry.path().join(format!("locales/{locale}.ftl")), - )?; - } + // Match cargo-vendor's uu_ directories and registry uu_- names. + let util_part = dir_name.split_once('-').map_or(dir_name, |(name, _)| name); + if let Some(util_name) = util_part.strip_prefix("uu_") { + embed_component_locales( + embedded_file, + locales_to_embed, + util_name, + |locale| entry.path().join(format!("locales/{locale}.ftl")), + )?; } } }