Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions fuzz/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

90 changes: 40 additions & 50 deletions src/bin/coreutils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -38,6 +37,20 @@ fn usage<T>(utils: &UtilityMap<T>, 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 <PREFIX><UTIL_NAME> 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();
Expand Down Expand Up @@ -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);
Expand All @@ -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 {
Expand Down
70 changes: 69 additions & 1 deletion tests/test_util_name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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())
Expand All @@ -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() {
Expand Down
10 changes: 6 additions & 4 deletions util/fetch-gnu.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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