diff --git a/Cargo.lock b/Cargo.lock index dd1bd5016b0..5dc4babe798 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1801,9 +1801,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.182" +version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "libloading" diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 35e7ed653b9..d53d6a3748d 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -1158,9 +1158,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.182" +version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "libfuzzer-sys" diff --git a/src/bin/coreutils.rs b/src/bin/coreutils.rs index 59634849a6b..429184f90a5 100644 --- a/src/bin/coreutils.rs +++ b/src/bin/coreutils.rs @@ -8,7 +8,6 @@ use coreutils::validation; use itertools::Itertools as _; use std::cmp; use std::ffi::OsString; -use std::io::{self, Write}; use std::process; use uucore::Args; @@ -38,6 +37,20 @@ fn usage(utils: &UtilityMap, name: &str) { ); } +/// Entry into Coreutils +/// +/// # Arguments +/// * first arg needs to be the binary/executable. \ +/// This is usually coreutils, but can be the util name itself, e.g. 'ls'. \ +/// The util name will be checked against the list of enabled utils, where +/// * the name exactly matches the name of an applet/util or +/// * the name matches pattern, e.g. +/// 'my_own_directory_service_ls' as long as the last letters match the utility. +/// * coreutils arg: --list, --version, -V, --help, -h (or shortened long versions): \ +/// Output information about coreutils itself. \ +/// * util name and any number of arguments: \ +/// Will get passed on to the selected utility. \ +/// Error if util name is not recognized. #[allow(clippy::cognitive_complexity)] fn main() { uucore::panic::mute_sigpipe_panic(); @@ -68,14 +81,30 @@ fn main() { validation::not_found(&OsString::from(binary_as_util)); }; - // 0th argument equals util name? + // 0th/1st argument equals util name? if let Some(util_os) = util_name { let Some(util) = util_os.to_str() else { + // Not UTF-8 validation::not_found(&util_os) }; - match util { - "--list" => { + // Util in known list? + if let Some(&(uumain, _)) = utils.get(util) { + // TODO: plug the deactivation of the translation + // and load the English strings directly at compilation time in the + // binary to avoid the load of the flt + // Could be something like: + // #[cfg(not(feature = "only_english"))] + validation::setup_localization_or_exit(util); + process::exit(uumain(vec![util_os].into_iter().chain(args))); + } else { + let l = util.len(); + // GNU coreutils --help string shows help for coreutils + if util == "-h" || (l <= 6 && util[0..l] == "--help"[0..l]) { + usage(&utils, binary_as_util); + process::exit(0); + // GNU coreutils --list string shows available utilities as list + } else if l <= 6 && util[0..l] == "--list"[0..l] { // If --help is also present, show usage instead of list if args.any(|arg| arg == "--help" || arg == "-h") { usage(&utils, binary_as_util); @@ -86,54 +115,15 @@ fn main() { println!("{util}"); } process::exit(0); - } - "--version" | "-V" => { + // GNU coreutils --version string shows version + } else if util == "-V" || (l <= 9 && util[0..l] == "--version"[0..l]) { println!("{binary_as_util} {VERSION} (multi-call binary)"); process::exit(0); - } - // Not a special command: fallthrough to calling a util - _ => {} - } - - match utils.get(util) { - Some(&(uumain, _)) => { - // TODO: plug the deactivation of the translation - // and load the English strings directly at compilation time in the - // binary to avoid the load of the flt - // Could be something like: - // #[cfg(not(feature = "only_english"))] - validation::setup_localization_or_exit(util); - process::exit(uumain(vec![util_os].into_iter().chain(args))); - } - None => { - if util == "--help" || util == "-h" { - // see if they want help on a specific util - if let Some(util_os) = args.next() { - let Some(util) = util_os.to_str() else { - validation::not_found(&util_os) - }; - - match utils.get(util) { - Some(&(uumain, _)) => { - let code = uumain( - vec![util_os, OsString::from("--help")] - .into_iter() - .chain(args), - ); - io::stdout().flush().expect("could not flush stdout"); - process::exit(code); - } - None => validation::not_found(&util_os), - } - } - usage(&utils, binary_as_util); - process::exit(0); - } else if util.starts_with('-') { - // Argument looks like an option but wasn't recognized - validation::unrecognized_option(binary_as_util, &util_os); - } else { - validation::not_found(&util_os); - } + } else if util.starts_with('-') { + // Argument looks like an option but wasn't recognized + validation::unrecognized_option(binary_as_util, &util_os); + } else { + validation::not_found(&util_os); } } } else { diff --git a/tests/test_util_name.rs b/tests/test_util_name.rs index 17b9dc36e5e..b412003559f 100644 --- a/tests/test_util_name.rs +++ b/tests/test_util_name.rs @@ -26,6 +26,24 @@ fn init() { eprintln!("Setting UUTESTS_BINARY_PATH={TESTS_BINARY}"); } +#[test] +fn test_coreutils_help_ignore_args() { + use std::process::Command; + + let scenario = TestScenario::new("help_ignoring_args"); + if !scenario.bin_path.exists() { + return; + } + + let output = Command::new(&scenario.bin_path) + .arg("--help") + .arg("---") + .output() + .unwrap(); + + assert_eq!(output.status.code(), Some(0)); +} + #[test] #[cfg(feature = "ls")] fn execution_phrase_double() { @@ -192,7 +210,7 @@ fn util_version() { println!("Skipping test: Binary not found at {:?}", scenario.bin_path); return; } - for arg in ["-V", "--version"] { + for arg in ["-V", "--version", "--ver"] { let child = Command::new(&scenario.bin_path) .arg(arg) .stdin(Stdio::piped()) @@ -209,6 +227,56 @@ fn util_version() { } } +#[test] +fn util_help() { + use std::process::{Command, Stdio}; + + let scenario = TestScenario::new("--version"); + if !scenario.bin_path.exists() { + println!("Skipping test: Binary not found at {:?}", scenario.bin_path); + return; + } + for arg in ["-h", "--help", "--he"] { + let child = Command::new(&scenario.bin_path) + .arg(arg) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let output = child.wait_with_output().unwrap(); + assert_eq!(output.status.code(), Some(0)); + assert_eq!(output.stderr, b""); + let output_str = String::from_utf8(output.stdout).unwrap(); + assert!(output_str.contains("Usage: coreutils")); + assert!(output_str.contains("lists all defined functions")); + } +} + +#[test] +fn util_arg_priority() { + use std::process::{Command, Stdio}; + + let scenario = TestScenario::new("--version"); + if !scenario.bin_path.exists() { + println!("Skipping test: Binary not found at {:?}", scenario.bin_path); + return; + } + let child = Command::new(&scenario.bin_path) + .arg("--list") + .arg("--help") + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let output = child.wait_with_output().unwrap(); + assert_eq!(output.status.code(), Some(0)); + assert_eq!(output.stderr, b""); + let output_str = String::from_utf8(output.stdout).unwrap(); + assert!(output_str.contains("Usage: coreutils")); + assert!(output_str.contains("lists all defined functions")); +} + #[test] #[cfg(target_env = "musl")] fn test_musl_no_dynamic_deps() { diff --git a/util/fetch-gnu.sh b/util/fetch-gnu.sh index afd9ebfa88a..9b7e1f50944 100755 --- a/util/fetch-gnu.sh +++ b/util/fetch-gnu.sh @@ -4,7 +4,9 @@ repo=https://github.com/coreutils/coreutils curl -L "${repo}/releases/download/v${ver}/coreutils-${ver}.tar.xz" | tar --strip-components=1 -xJf - # TODO stop backporting tests from master at GNU coreutils > $ver -# backport = () -# for f in ${backport[@]} -# do curl -L ${repo}/raw/refs/heads/master/tests/$f > tests/$f -# done + backport=( + misc/coreutils.sh +) + for f in "${backport[@]}" + do curl -L ${repo}/raw/refs/heads/master/tests/$f > tests/$f + done