diff --git a/Cargo.lock b/Cargo.lock index 256ad39a..b25e70fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -549,9 +549,10 @@ dependencies = [ "onig", "regex", "rstest", + "rustix", "tempfile", "thiserror", - "uucore 0.10.0", + "uucore", "uutests", "walkdir", ] @@ -1562,39 +1563,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" -[[package]] -name = "uucore" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e97dd0fd8bb2ed12affba9f956c3b94b8ad3f9432746717475420e916ae0b8d4" -dependencies = [ - "bstr", - "clap", - "dunce", - "fluent", - "fluent-syntax", - "libc", - "nix 0.31.3", - "os_display", - "rustc-hash", - "rustix", - "thiserror", - "unic-langid", - "unicode-width", - "uucore_procs 0.10.0", - "wild", - "winapi-util", - "windows-sys 0.61.2", -] - [[package]] name = "uucore" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f24a5910ebbf2c0baf486a9125705f8e026a8f7741a3ea8cf5eca3c9257c9bdf" dependencies = [ + "bstr", "clap", "dns-lookup", + "dunce", "fluent", "fluent-syntax", "jiff", @@ -1607,21 +1585,11 @@ dependencies = [ "time", "unic-langid", "unicode-width", - "uucore_procs 0.12.0", + "uucore_procs", "wild", "windows-sys 0.61.2", ] -[[package]] -name = "uucore_procs" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "907f1b828046ea42cdb4bd898f05b48ab873a41937bf75ebe12f09a5bf411f58" -dependencies = [ - "proc-macro2", - "quote", -] - [[package]] name = "uucore_procs" version = "0.12.0" @@ -1646,7 +1614,7 @@ dependencies = [ "regex", "rlimit", "tempfile", - "uucore 0.12.0", + "uucore", "xattr", ] diff --git a/Cargo.toml b/Cargo.toml index f98c3242..dd4992b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,11 +24,17 @@ faccess = "0.2.4" nix = { version = "0.31", features = ["fs", "user"] } onig = { version = "6.5", default-features = false } regex = "1.12" -uucore = { version = "0.10.0", features = ["entries", "fs", "fsext", "mode"] } +uucore = { version = "0.12.0", features = ["entries", "fs", "fsext", "mode"] } walkdir = "2.5" itertools = "0.15.0" thiserror = "2.0.12" +# uucore depends on rustix without its `std` feature, which leaves +# `uucore::fs` unable to build for WASI. Turn it on through feature +# unification until uucore does it itself. +[target.'cfg(target_os = "wasi")'.dependencies] +rustix = { version = "1.1.4", default-features = false, features = ["std"] } + [dev-dependencies] criterion = { version = "4.7.0", package = "codspeed-criterion-compat" } assert_cmd = "2" diff --git a/src/find/matchers/mod.rs b/src/find/matchers/mod.rs index a450ea80..a33d47c8 100644 --- a/src/find/matchers/mod.rs +++ b/src/find/matchers/mod.rs @@ -1740,7 +1740,7 @@ mod tests { fn build_top_level_matcher_perm_bad() { let mut config = Config::default(); if let Err(e) = build_top_level_matcher(&["-perm", "foo"], &mut config) { - assert!(e.to_string().contains("invalid operator")); + assert_eq!(e.to_string(), "invalid mode 'foo'"); } else { panic!("-perm with bad mode pattern should fail"); } diff --git a/src/find/matchers/perm.rs b/src/find/matchers/perm.rs index 2c66089f..93ba4471 100644 --- a/src/find/matchers/perm.rs +++ b/src/find/matchers/perm.rs @@ -40,7 +40,7 @@ impl ComparisonType { #[cfg(unix)] mod parsing { - use super::{parse_numeric, parse_symbolic, ComparisonType, Error}; + use super::{parse_numeric, parse_symbolic, ComparisonType}; pub fn split_comparison_type(pattern: &str) -> (ComparisonType, &str) { let mut chars = pattern.chars(); @@ -52,24 +52,20 @@ mod parsing { } } - pub fn parse_mode(pattern: &str, for_dir: bool) -> Result> { + pub fn parse_mode(pattern: &str, for_dir: bool) -> Option { // GNU rejects the old `-perm +MODE` octal form; a leading + needs a symbolic mode. - if let Some(rest) = pattern.strip_prefix('+') { - if rest.contains(|c: char| c.is_ascii_digit()) { - return Err(From::from(format!("invalid mode '+{rest}'"))); - } - } - - let mode = if pattern.contains(|c: char| c.is_ascii_digit()) { - parse_numeric(0, pattern, for_dir)? + let plus_octal = pattern + .strip_prefix('+') + .is_some_and(|rest| rest.contains(|c: char| c.is_ascii_digit())); + if plus_octal { + None + } else if pattern.contains(|c: char| c.is_ascii_digit()) { + parse_numeric(0, pattern, for_dir).ok() } else { - let mut mode = 0; - for chunk in pattern.split(',') { - mode = parse_symbolic(mode, chunk, 0, for_dir)?; - } - mode - }; - Ok(mode) + pattern.split(',').try_fold(0, |mode, chunk| { + parse_symbolic(mode, chunk, 0, for_dir).ok() + }) + } } } @@ -87,9 +83,11 @@ pub struct PermMatcher {} impl PermMatcher { #[cfg(unix)] pub fn new(pattern: &str) -> Result> { - let (comparison_type, pattern) = parsing::split_comparison_type(pattern); - let file_pattern = parsing::parse_mode(pattern, false)?; - let dir_pattern = parsing::parse_mode(pattern, false)?; + // Like GNU, name the whole argument rather than say what is wrong with it. + let invalid_mode = || format!("invalid mode '{pattern}'"); + let (comparison_type, mode) = parsing::split_comparison_type(pattern); + let file_pattern = parsing::parse_mode(mode, false).ok_or_else(invalid_mode)?; + let dir_pattern = parsing::parse_mode(mode, false).ok_or_else(invalid_mode)?; Ok(Self { comparison_type, file_pattern, @@ -236,6 +234,16 @@ mod tests { // .expect_err("missing comma should fail"); } + #[test] + fn parsing_failure_names_the_mode() { + // Whatever the parser choked on, the message is the same plain English + // line: it must not depend on uucore's localizer being set up. + for pattern in ["g+q", "a", "-0o9", "/u=z", "+17"] { + let error = PermMatcher::new(pattern).expect_err("mode should be rejected"); + assert_eq!(error.to_string(), format!("invalid mode '{pattern}'")); + } + } + #[test] fn comparison_type_matching() { let c = ComparisonType::Exact;