From abc5cfb9508c4d139b39bff335c4aa6a288a7886 Mon Sep 17 00:00:00 2001 From: Bart Massey Date: Tue, 14 Feb 2023 00:21:16 -0800 Subject: [PATCH 001/700] coreutils: fixed panic when multi-call binary has empty or non-UTF-8 name The multi-call `coreutils` binary starts by trying to convert its invocation path into a UTF-8 string, panicking if this doesn't work. This patch makes `coreutils` exit gracefully in this case. --- src/bin/coreutils.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/bin/coreutils.rs b/src/bin/coreutils.rs index 08c0774ba7f..75e64e17bae 100644 --- a/src/bin/coreutils.rs +++ b/src/bin/coreutils.rs @@ -41,8 +41,8 @@ fn binary_path(args: &mut impl Iterator) -> PathBuf { } } -fn name(binary_path: &Path) -> &str { - binary_path.file_stem().unwrap().to_str().unwrap() +fn name(binary_path: &Path) -> Option<&str> { + binary_path.file_stem()?.to_str() } fn main() { @@ -52,7 +52,10 @@ fn main() { let mut args = uucore::args_os(); let binary = binary_path(&mut args); - let binary_as_util = name(&binary); + let binary_as_util = name(&binary).unwrap_or_else(|| { + usage(&utils, ""); + process::exit(0); + }); // binary name equals util name? if let Some(&(uumain, _)) = utils.get(binary_as_util) { From af4ce911c8315e3f339ea809ccf04b65ef834075 Mon Sep 17 00:00:00 2001 From: ZauJulio Date: Thu, 16 Feb 2023 17:48:23 -0300 Subject: [PATCH 002/700] mktemp: fix PrefixContainsDirSeparator verification --- src/uu/mktemp/src/mktemp.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/uu/mktemp/src/mktemp.rs b/src/uu/mktemp/src/mktemp.rs index 390c77bfafb..fed946577a0 100644 --- a/src/uu/mktemp/src/mktemp.rs +++ b/src/uu/mktemp/src/mktemp.rs @@ -281,7 +281,11 @@ impl Params { .join(prefix_from_template) .display() .to_string(); - if options.treat_as_template && prefix.contains(MAIN_SEPARATOR) { + + // Check that the prefix is valid. + let prefix_of_template = Path::new(prefix_from_template).display().to_string(); + + if options.treat_as_template && prefix_of_template.contains(MAIN_SEPARATOR) { return Err(MkTempError::PrefixContainsDirSeparator(options.template)); } if tmpdir.is_some() && Path::new(prefix_from_template).is_absolute() { From 9e2c543cd40c6b237097f17ae66141e8fc9d4413 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Za=C3=BA=20J=C3=BAlio?= Date: Thu, 16 Feb 2023 18:43:32 -0300 Subject: [PATCH 003/700] mktemp: prefix prefix_of_template use to_string_lossy Co-authored-by: Terts Diepraam --- src/uu/mktemp/src/mktemp.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/mktemp/src/mktemp.rs b/src/uu/mktemp/src/mktemp.rs index fed946577a0..1764b8e84dd 100644 --- a/src/uu/mktemp/src/mktemp.rs +++ b/src/uu/mktemp/src/mktemp.rs @@ -283,7 +283,7 @@ impl Params { .to_string(); // Check that the prefix is valid. - let prefix_of_template = Path::new(prefix_from_template).display().to_string(); + let prefix_of_template = Path::new(prefix_from_template).to_string_lossy(); if options.treat_as_template && prefix_of_template.contains(MAIN_SEPARATOR) { return Err(MkTempError::PrefixContainsDirSeparator(options.template)); From ff6f61276e0a077a6cc608dc616f71e9e0294ecf Mon Sep 17 00:00:00 2001 From: curtain Date: Mon, 20 Feb 2023 14:25:00 +0800 Subject: [PATCH 004/700] basename: move help strings to markdown file --- src/uu/basename/basename.md | 12 ++++++++++++ src/uu/basename/src/basename.rs | 8 +++----- 2 files changed, 15 insertions(+), 5 deletions(-) create mode 100644 src/uu/basename/basename.md diff --git a/src/uu/basename/basename.md b/src/uu/basename/basename.md new file mode 100644 index 00000000000..6af001323e0 --- /dev/null +++ b/src/uu/basename/basename.md @@ -0,0 +1,12 @@ +# basename + +## Usage +``` +basename NAME [SUFFIX] +basename OPTION... NAME... +``` + +## About + +Print NAME with any leading directory components removed +If specified, also remove a trailing SUFFIX diff --git a/src/uu/basename/src/basename.rs b/src/uu/basename/src/basename.rs index 613c4d67cbd..bbc8abf75f4 100644 --- a/src/uu/basename/src/basename.rs +++ b/src/uu/basename/src/basename.rs @@ -11,13 +11,11 @@ use clap::{crate_version, Arg, ArgAction, Command}; use std::path::{is_separator, PathBuf}; use uucore::display::Quotable; use uucore::error::{UResult, UUsageError}; -use uucore::format_usage; +use uucore::{format_usage, help_usage, help_section}; -static ABOUT: &str = r#"Print NAME with any leading directory components removed -If specified, also remove a trailing SUFFIX"#; +static ABOUT: &str = help_section!("about","basename.md"); -const USAGE: &str = "{} NAME [SUFFIX] - {} OPTION... NAME..."; +const USAGE: &str = help_usage!("basename.md"); pub mod options { pub static MULTIPLE: &str = "multiple"; From 3acd75bcc4d73df4016c78b45c8fa3dce1f31832 Mon Sep 17 00:00:00 2001 From: Thayne McCombs Date: Sun, 12 Feb 2023 01:36:13 -0700 Subject: [PATCH 005/700] cp: Use PathBuf instead of String for paths I think this should avoid unnecessarily validating utf-8, and avoids a few allocations. --- src/uu/cp/src/cp.rs | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index f0dc762be10..23428956492 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -26,7 +26,7 @@ use std::os::unix::fs::{FileTypeExt, PermissionsExt}; use std::path::{Path, PathBuf, StripPrefixError}; use std::string::ToString; -use clap::{crate_version, Arg, ArgAction, ArgMatches, Command}; +use clap::{builder::ValueParser, crate_version, Arg, ArgAction, ArgMatches, Command}; use filetime::FileTime; use indicatif::{ProgressBar, ProgressStyle}; #[cfg(unix)] @@ -91,7 +91,7 @@ quick_error! { /// Invalid arguments to backup Backup(description: String) { display("{}\nTry '{} --help' for more information.", description, uucore::execution_phrase()) } - NotADirectory(path: String) { display("'{}' is not a directory", path) } + NotADirectory(path: PathBuf) { display("'{}' is not a directory", path.display()) } } } @@ -222,7 +222,7 @@ pub struct Options { attributes: Attributes, recursive: bool, backup_suffix: String, - target_dir: Option, + target_dir: Option, update: bool, verbose: bool, progress_bar: bool, @@ -302,6 +302,7 @@ pub fn uu_app() -> Command { .long(options::TARGET_DIRECTORY) .value_name(options::TARGET_DIRECTORY) .value_hint(clap::ValueHint::DirPath) + .value_parser(ValueParser::path_buf()) .help("copy all SOURCE arguments into target-directory"), ) .arg( @@ -555,7 +556,8 @@ pub fn uu_app() -> Command { .arg( Arg::new(options::PATHS) .action(ArgAction::Append) - .value_hint(clap::ValueHint::AnyPath), + .value_hint(clap::ValueHint::AnyPath) + .value_parser(ValueParser::path_buf()), ) } @@ -576,7 +578,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { clap::error::ErrorKind::DisplayVersion => print!("{}", app.render_version()), _ => return Err(Box::new(e.with_exit_code(1))), }; - } else if let Ok(matches) = matches { + } else if let Ok(mut matches) = matches { let options = Options::from_matches(&matches)?; if options.overwrite == OverwriteMode::NoClobber && options.backup != BackupMode::NoBackup { @@ -586,12 +588,12 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { )); } - let paths: Vec = matches - .get_many::(options::PATHS) - .map(|v| v.map(ToString::to_string).collect()) + let paths: Vec = matches + .remove_many::(options::PATHS) + .map(|v| v.collect()) .unwrap_or_default(); - let (sources, target) = parse_path_args(&paths, &options)?; + let (sources, target) = parse_path_args(paths, &options)?; if let Err(error) = copy(&sources, &target, &options) { match error { @@ -754,11 +756,11 @@ impl Options { // Parse target directory options let no_target_dir = matches.get_flag(options::NO_TARGET_DIRECTORY); let target_dir = matches - .get_one::(options::TARGET_DIRECTORY) - .map(ToString::to_string); + .get_one::(options::TARGET_DIRECTORY) + .cloned(); if let Some(dir) = &target_dir { - if !Path::new(dir).is_dir() { + if !dir.is_dir() { return Err(Error::NotADirectory(dir.clone())); } }; @@ -915,9 +917,7 @@ impl TargetType { } /// Returns tuple of (Source paths, Target) -fn parse_path_args(path_args: &[String], options: &Options) -> CopyResult<(Vec, Target)> { - let mut paths = path_args.iter().map(PathBuf::from).collect::>(); - +fn parse_path_args(mut paths: Vec, options: &Options) -> CopyResult<(Vec, Target)> { if paths.is_empty() { // No files specified return Err("missing file operand".into()); @@ -933,7 +933,7 @@ fn parse_path_args(path_args: &[String], options: &Options) -> CopyResult<(Vec { // All path args are sources, and the target dir was // specified separately - PathBuf::from(target) + target.clone() } None => { // If there was no explicit target-dir, then use the last From f70d23b0182dafb87c9b47e1ac82b42aeba8cacc Mon Sep 17 00:00:00 2001 From: curtain Date: Mon, 20 Feb 2023 18:13:36 +0800 Subject: [PATCH 006/700] basename: update format according to #4385 --- src/uu/basename/basename.md | 3 --- src/uu/basename/src/basename.rs | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/uu/basename/basename.md b/src/uu/basename/basename.md index 6af001323e0..b17cac74a00 100644 --- a/src/uu/basename/basename.md +++ b/src/uu/basename/basename.md @@ -1,12 +1,9 @@ # basename -## Usage ``` basename NAME [SUFFIX] basename OPTION... NAME... ``` -## About - Print NAME with any leading directory components removed If specified, also remove a trailing SUFFIX diff --git a/src/uu/basename/src/basename.rs b/src/uu/basename/src/basename.rs index bbc8abf75f4..48f00f4b886 100644 --- a/src/uu/basename/src/basename.rs +++ b/src/uu/basename/src/basename.rs @@ -11,9 +11,9 @@ use clap::{crate_version, Arg, ArgAction, Command}; use std::path::{is_separator, PathBuf}; use uucore::display::Quotable; use uucore::error::{UResult, UUsageError}; -use uucore::{format_usage, help_usage, help_section}; +use uucore::{format_usage, help_usage, help_about}; -static ABOUT: &str = help_section!("about","basename.md"); +static ABOUT: &str = help_about!("basename.md"); const USAGE: &str = help_usage!("basename.md"); From 17f9507e17967738e6ed9d5b9385c998de2c5537 Mon Sep 17 00:00:00 2001 From: Thayne McCombs Date: Mon, 13 Feb 2023 02:00:03 -0700 Subject: [PATCH 007/700] Add tests for non-utf8 --- tests/by-util/test_chown.rs | 8 ++-- tests/by-util/test_chroot.rs | 2 +- tests/by-util/test_cp.rs | 82 +++++++++++++++++++++++++--------- tests/by-util/test_install.rs | 58 ++++++++++++------------ tests/by-util/test_ln.rs | 2 +- tests/by-util/test_ls.rs | 72 ++++++++++++++--------------- tests/by-util/test_mv.rs | 56 +++++++++++------------ tests/by-util/test_realpath.rs | 2 +- tests/common/util.rs | 14 +++--- 9 files changed, 170 insertions(+), 126 deletions(-) diff --git a/tests/by-util/test_chown.rs b/tests/by-util/test_chown.rs index 5237a7cf7b0..92ee55b1ccd 100644 --- a/tests/by-util/test_chown.rs +++ b/tests/by-util/test_chown.rs @@ -662,10 +662,10 @@ fn test_chown_recursive() { at.mkdir_all("a/b/c"); at.mkdir("z"); - at.touch(&at.plus_as_string("a/a")); - at.touch(&at.plus_as_string("a/b/b")); - at.touch(&at.plus_as_string("a/b/c/c")); - at.touch(&at.plus_as_string("z/y")); + at.touch(at.plus_as_string("a/a")); + at.touch(at.plus_as_string("a/b/b")); + at.touch(at.plus_as_string("a/b/c/c")); + at.touch(at.plus_as_string("z/y")); let result = scene .ucmd() diff --git a/tests/by-util/test_chroot.rs b/tests/by-util/test_chroot.rs index c7e786f9325..0e34687203d 100644 --- a/tests/by-util/test_chroot.rs +++ b/tests/by-util/test_chroot.rs @@ -39,7 +39,7 @@ fn test_enter_chroot_fails() { fn test_no_such_directory() { let (at, mut ucmd) = at_and_ucmd!(); - at.touch(&at.plus_as_string("a")); + at.touch(at.plus_as_string("a")); ucmd.arg("a") .fails() diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index e6e8a6ba2ab..818261220d0 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -15,11 +15,15 @@ use std::os::unix::fs::PermissionsExt; use std::os::windows::fs::symlink_file; #[cfg(not(windows))] use std::path::Path; +#[cfg(target_os = "linux")] +use std::path::PathBuf; #[cfg(any(target_os = "linux", target_os = "android"))] use filetime::FileTime; #[cfg(any(target_os = "linux", target_os = "android"))] use rlimit::Resource; +#[cfg(target_os = "linux")] +use std::ffi::OsString; #[cfg(any(target_os = "linux", target_os = "android"))] use std::fs as std_fs; use std::thread::sleep; @@ -91,7 +95,7 @@ fn test_cp_existing_target() { assert_eq!(at.read(TEST_EXISTING_FILE), "Hello, World!\n"); // No backup should have been created - assert!(!at.file_exists(&format!("{TEST_EXISTING_FILE}~"))); + assert!(!at.file_exists(format!("{TEST_EXISTING_FILE}~"))); } #[test] @@ -636,7 +640,7 @@ fn test_cp_backup_none() { .no_stderr(); assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n"); - assert!(!at.file_exists(&format!("{TEST_HOW_ARE_YOU_SOURCE}~"))); + assert!(!at.file_exists(format!("{TEST_HOW_ARE_YOU_SOURCE}~"))); } #[test] @@ -650,7 +654,7 @@ fn test_cp_backup_off() { .no_stderr(); assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n"); - assert!(!at.file_exists(&format!("{TEST_HOW_ARE_YOU_SOURCE}~"))); + assert!(!at.file_exists(format!("{TEST_HOW_ARE_YOU_SOURCE}~"))); } #[test] @@ -700,7 +704,7 @@ fn test_cp_deref() { .join(TEST_HELLO_WORLD_SOURCE_SYMLINK); // unlike -P/--no-deref, we expect a file, not a link assert!(at.file_exists( - &path_to_new_symlink + path_to_new_symlink .clone() .into_os_string() .into_string() @@ -1062,7 +1066,7 @@ fn test_cp_deref_folder_to_folder() { .join(TEST_COPY_TO_FOLDER_NEW) .join(TEST_HELLO_WORLD_SOURCE_SYMLINK); assert!(at.file_exists( - &path_to_new_symlink + path_to_new_symlink .clone() .into_os_string() .into_string() @@ -1225,8 +1229,8 @@ fn test_cp_archive_recursive() { let file_2 = at.subdir.join(TEST_COPY_TO_FOLDER).join("2"); let file_2_link = at.subdir.join(TEST_COPY_TO_FOLDER).join("2.link"); - at.touch(&file_1.to_string_lossy()); - at.touch(&file_2.to_string_lossy()); + at.touch(file_1); + at.touch(file_2); at.symlink_file("1", &file_1_link.to_string_lossy()); at.symlink_file("2", &file_2_link.to_string_lossy()); @@ -1252,18 +1256,8 @@ fn test_cp_archive_recursive() { .run(); println!("ls dest {}", result.stdout_str()); - assert!(at.file_exists( - &at.subdir - .join(TEST_COPY_TO_FOLDER_NEW) - .join("1") - .to_string_lossy() - )); - assert!(at.file_exists( - &at.subdir - .join(TEST_COPY_TO_FOLDER_NEW) - .join("2") - .to_string_lossy() - )); + assert!(at.file_exists(at.subdir.join(TEST_COPY_TO_FOLDER_NEW).join("1"))); + assert!(at.file_exists(at.subdir.join(TEST_COPY_TO_FOLDER_NEW).join("2"))); assert!(at.is_symlink( &at.subdir @@ -1672,7 +1666,7 @@ fn test_cp_reflink_always_override() { let dst_path: &str = &vec![MOUNTPOINT, USERDIR, "dst"].concat(); scene.fixtures.mkdir(ROOTDIR); - scene.fixtures.mkdir(&vec![ROOTDIR, USERDIR].concat()); + scene.fixtures.mkdir(vec![ROOTDIR, USERDIR].concat()); // Setup: // Because neither `mkfs.btrfs` not btrfs `mount` options allow us to have a mountpoint owned @@ -2532,3 +2526,51 @@ fn test_src_base_dot() { .no_stdout(); assert!(!at.dir_exists("y/x")); } + +#[cfg(target_os = "linux")] +fn non_utf8_name(suffix: &str) -> OsString { + use std::os::unix::ffi::OsStringExt; + let mut name = OsString::from_vec(vec![0xff, 0xff]); + name.push(suffix); + name +} + +#[cfg(target_os = "linux")] +#[test] +fn test_non_utf8_src() { + let (at, mut ucmd) = at_and_ucmd!(); + let src = non_utf8_name("src"); + std::fs::File::create(at.plus(&src)).unwrap(); + ucmd.args(&[src, "dest".into()]) + .succeeds() + .no_stderr() + .no_stdout(); + assert!(at.file_exists("dest")); +} + +#[cfg(target_os = "linux")] +#[test] +fn test_non_utf8_dest() { + let (at, mut ucmd) = at_and_ucmd!(); + let dest = non_utf8_name("dest"); + ucmd.args(&[TEST_HELLO_WORLD_SOURCE.as_ref(), &*dest]) + .succeeds() + .no_stderr() + .no_stdout(); + assert!(at.file_exists(dest)); +} + +#[cfg(target_os = "linux")] +#[test] +fn test_non_utf8_target() { + let (at, mut ucmd) = at_and_ucmd!(); + let dest = non_utf8_name("dest"); + at.mkdir(&dest); + ucmd.args(&["-t".as_ref(), &*dest, TEST_HELLO_WORLD_SOURCE.as_ref()]) + .succeeds() + .no_stderr() + .no_stdout(); + let mut copied_file = PathBuf::from(dest); + copied_file.push(TEST_HELLO_WORLD_SOURCE); + assert!(at.file_exists(copied_file)); +} diff --git a/tests/by-util/test_install.rs b/tests/by-util/test_install.rs index e041fb081f4..133f1381db7 100644 --- a/tests/by-util/test_install.rs +++ b/tests/by-util/test_install.rs @@ -28,8 +28,8 @@ fn test_install_basic() { assert!(at.file_exists(file1)); assert!(at.file_exists(file2)); - assert!(at.file_exists(&format!("{dir}/{file1}"))); - assert!(at.file_exists(&format!("{dir}/{file2}"))); + assert!(at.file_exists(format!("{dir}/{file1}"))); + assert!(at.file_exists(format!("{dir}/{file2}"))); } #[test] @@ -76,7 +76,7 @@ fn test_install_unimplemented_arg() { .fails() .stderr_contains("Unimplemented"); - assert!(!at.file_exists(&format!("{dir}/{file}"))); + assert!(!at.file_exists(format!("{dir}/{file}"))); } #[test] @@ -314,7 +314,7 @@ fn test_install_target_new_file() { .no_stderr(); assert!(at.file_exists(file)); - assert!(at.file_exists(&format!("{dir}/{file}"))); + assert!(at.file_exists(format!("{dir}/{file}"))); } #[test] @@ -341,7 +341,7 @@ fn test_install_target_new_file_with_group() { result.success(); assert!(at.file_exists(file)); - assert!(at.file_exists(&format!("{dir}/{file}"))); + assert!(at.file_exists(format!("{dir}/{file}"))); } #[test] @@ -368,7 +368,7 @@ fn test_install_target_new_file_with_owner() { result.success(); assert!(at.file_exists(file)); - assert!(at.file_exists(&format!("{dir}/{file}"))); + assert!(at.file_exists(format!("{dir}/{file}"))); } #[test] @@ -447,13 +447,13 @@ fn test_install_nested_paths_copy_file() { at.mkdir(dir1); at.mkdir(dir2); - at.touch(&format!("{dir1}/{file1}")); + at.touch(format!("{dir1}/{file1}")); ucmd.arg(format!("{dir1}/{file1}")) .arg(dir2) .succeeds() .no_stderr(); - assert!(at.file_exists(&format!("{dir2}/{file1}"))); + assert!(at.file_exists(format!("{dir2}/{file1}"))); } #[test] @@ -487,7 +487,7 @@ fn test_install_failing_omitting_directory() { .fails() .code_is(1) .stderr_contains("omitting directory"); - assert!(at.file_exists(&format!("{dir3}/{file1}"))); + assert!(at.file_exists(format!("{dir3}/{file1}"))); // install also fails, when only one source param is given scene @@ -785,7 +785,7 @@ fn test_install_creating_leading_dirs_with_single_source_and_target_dir() { .succeeds() .no_stderr(); - assert!(at.file_exists(&format!("{target_dir}/{source1}"))); + assert!(at.file_exists(format!("{target_dir}/{source1}"))); } #[test] @@ -863,8 +863,8 @@ fn test_install_dir() { assert!(at.file_exists(file1)); assert!(at.file_exists(file2)); - assert!(at.file_exists(&format!("{dir}/{file1}"))); - assert!(at.file_exists(&format!("{dir}/{file2}"))); + assert!(at.file_exists(format!("{dir}/{file1}"))); + assert!(at.file_exists(format!("{dir}/{file2}"))); } // // test backup functionality @@ -888,7 +888,7 @@ fn test_install_backup_short_no_args_files() { assert!(at.file_exists(file_a)); assert!(at.file_exists(file_b)); - assert!(at.file_exists(&format!("{file_b}~"))); + assert!(at.file_exists(format!("{file_b}~"))); } #[test] @@ -913,7 +913,7 @@ fn test_install_backup_short_no_args_file_to_dir() { assert!(at.file_exists(file)); assert!(at.file_exists(&expect)); - assert!(at.file_exists(&format!("{expect}~"))); + assert!(at.file_exists(format!("{expect}~"))); } // Long --backup option is tested separately as it requires a slightly different @@ -938,7 +938,7 @@ fn test_install_backup_long_no_args_files() { assert!(at.file_exists(file_a)); assert!(at.file_exists(file_b)); - assert!(at.file_exists(&format!("{file_b}~"))); + assert!(at.file_exists(format!("{file_b}~"))); } #[test] @@ -963,7 +963,7 @@ fn test_install_backup_long_no_args_file_to_dir() { assert!(at.file_exists(file)); assert!(at.file_exists(&expect)); - assert!(at.file_exists(&format!("{expect}~"))); + assert!(at.file_exists(format!("{expect}~"))); } #[test] @@ -988,7 +988,7 @@ fn test_install_backup_short_custom_suffix() { assert!(at.file_exists(file_a)); assert!(at.file_exists(file_b)); - assert!(at.file_exists(&format!("{file_b}{suffix}"))); + assert!(at.file_exists(format!("{file_b}{suffix}"))); } #[test] @@ -1013,7 +1013,7 @@ fn test_install_backup_short_custom_suffix_hyphen_value() { assert!(at.file_exists(file_a)); assert!(at.file_exists(file_b)); - assert!(at.file_exists(&format!("{file_b}{suffix}"))); + assert!(at.file_exists(format!("{file_b}{suffix}"))); } #[test] @@ -1038,7 +1038,7 @@ fn test_install_backup_custom_suffix_via_env() { assert!(at.file_exists(file_a)); assert!(at.file_exists(file_b)); - assert!(at.file_exists(&format!("{file_b}{suffix}"))); + assert!(at.file_exists(format!("{file_b}{suffix}"))); } #[test] @@ -1061,7 +1061,7 @@ fn test_install_backup_numbered_with_t() { assert!(at.file_exists(file_a)); assert!(at.file_exists(file_b)); - assert!(at.file_exists(&format!("{file_b}.~1~"))); + assert!(at.file_exists(format!("{file_b}.~1~"))); } #[test] @@ -1084,7 +1084,7 @@ fn test_install_backup_numbered_with_numbered() { assert!(at.file_exists(file_a)); assert!(at.file_exists(file_b)); - assert!(at.file_exists(&format!("{file_b}.~1~"))); + assert!(at.file_exists(format!("{file_b}.~1~"))); } #[test] @@ -1107,7 +1107,7 @@ fn test_install_backup_existing() { assert!(at.file_exists(file_a)); assert!(at.file_exists(file_b)); - assert!(at.file_exists(&format!("{file_b}~"))); + assert!(at.file_exists(format!("{file_b}~"))); } #[test] @@ -1130,7 +1130,7 @@ fn test_install_backup_nil() { assert!(at.file_exists(file_a)); assert!(at.file_exists(file_b)); - assert!(at.file_exists(&format!("{file_b}~"))); + assert!(at.file_exists(format!("{file_b}~"))); } #[test] @@ -1156,7 +1156,7 @@ fn test_install_backup_numbered_if_existing_backup_existing() { assert!(at.file_exists(file_a)); assert!(at.file_exists(file_b)); assert!(at.file_exists(file_b_backup)); - assert!(at.file_exists(&format!("{file_b}.~2~"))); + assert!(at.file_exists(format!("{file_b}.~2~"))); } #[test] @@ -1182,7 +1182,7 @@ fn test_install_backup_numbered_if_existing_backup_nil() { assert!(at.file_exists(file_a)); assert!(at.file_exists(file_b)); assert!(at.file_exists(file_b_backup)); - assert!(at.file_exists(&format!("{file_b}.~2~"))); + assert!(at.file_exists(format!("{file_b}.~2~"))); } #[test] @@ -1205,7 +1205,7 @@ fn test_install_backup_simple() { assert!(at.file_exists(file_a)); assert!(at.file_exists(file_b)); - assert!(at.file_exists(&format!("{file_b}~"))); + assert!(at.file_exists(format!("{file_b}~"))); } #[test] @@ -1228,7 +1228,7 @@ fn test_install_backup_never() { assert!(at.file_exists(file_a)); assert!(at.file_exists(file_b)); - assert!(at.file_exists(&format!("{file_b}~"))); + assert!(at.file_exists(format!("{file_b}~"))); } #[test] @@ -1251,7 +1251,7 @@ fn test_install_backup_none() { assert!(at.file_exists(file_a)); assert!(at.file_exists(file_b)); - assert!(!at.file_exists(&format!("{file_b}~"))); + assert!(!at.file_exists(format!("{file_b}~"))); } #[test] @@ -1274,7 +1274,7 @@ fn test_install_backup_off() { assert!(at.file_exists(file_a)); assert!(at.file_exists(file_b)); - assert!(!at.file_exists(&format!("{file_b}~"))); + assert!(!at.file_exists(format!("{file_b}~"))); } #[test] diff --git a/tests/by-util/test_ln.rs b/tests/by-util/test_ln.rs index bc51fc10747..1a4c9095b5c 100644 --- a/tests/by-util/test_ln.rs +++ b/tests/by-util/test_ln.rs @@ -403,7 +403,7 @@ fn test_symlink_implicit_target_dir() { let file = &path.to_string_lossy(); at.mkdir(dir); - at.touch(file); + at.touch(&path); ucmd.args(&["-s", file]).succeeds().no_stderr(); diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 339c78c1214..36ae816b30d 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -609,10 +609,10 @@ fn test_ls_a() { fn test_ls_width() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; - at.touch(&at.plus_as_string("test-width-1")); - at.touch(&at.plus_as_string("test-width-2")); - at.touch(&at.plus_as_string("test-width-3")); - at.touch(&at.plus_as_string("test-width-4")); + at.touch(at.plus_as_string("test-width-1")); + at.touch(at.plus_as_string("test-width-2")); + at.touch(at.plus_as_string("test-width-3")); + at.touch(at.plus_as_string("test-width-4")); for option in [ "-w 100", @@ -692,10 +692,10 @@ fn test_ls_width() { fn test_ls_columns() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; - at.touch(&at.plus_as_string("test-columns-1")); - at.touch(&at.plus_as_string("test-columns-2")); - at.touch(&at.plus_as_string("test-columns-3")); - at.touch(&at.plus_as_string("test-columns-4")); + at.touch(at.plus_as_string("test-columns-1")); + at.touch(at.plus_as_string("test-columns-2")); + at.touch(at.plus_as_string("test-columns-3")); + at.touch(at.plus_as_string("test-columns-4")); // Columns is the default let result = scene.ucmd().succeeds(); @@ -753,10 +753,10 @@ fn test_ls_columns() { fn test_ls_across() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; - at.touch(&at.plus_as_string("test-across-1")); - at.touch(&at.plus_as_string("test-across-2")); - at.touch(&at.plus_as_string("test-across-3")); - at.touch(&at.plus_as_string("test-across-4")); + at.touch(at.plus_as_string("test-across-1")); + at.touch(at.plus_as_string("test-across-2")); + at.touch(at.plus_as_string("test-across-3")); + at.touch(at.plus_as_string("test-across-4")); for option in ACROSS_ARGS { let result = scene.ucmd().arg(option).succeeds(); @@ -781,10 +781,10 @@ fn test_ls_across() { fn test_ls_commas() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; - at.touch(&at.plus_as_string("test-commas-1")); - at.touch(&at.plus_as_string("test-commas-2")); - at.touch(&at.plus_as_string("test-commas-3")); - at.touch(&at.plus_as_string("test-commas-4")); + at.touch(at.plus_as_string("test-commas-1")); + at.touch(at.plus_as_string("test-commas-2")); + at.touch(at.plus_as_string("test-commas-3")); + at.touch(at.plus_as_string("test-commas-4")); for option in COMMA_ARGS { let result = scene.ucmd().arg(option).succeeds(); @@ -814,8 +814,8 @@ fn test_ls_zero() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; at.mkdir("0-test-zero"); - at.touch(&at.plus_as_string("2-test-zero")); - at.touch(&at.plus_as_string("3-test-zero")); + at.touch(at.plus_as_string("2-test-zero")); + at.touch(at.plus_as_string("3-test-zero")); let ignored_opts = [ "--quoting-style=c", @@ -870,7 +870,7 @@ fn test_ls_zero() { #[cfg(unix)] { - at.touch(&at.plus_as_string("1\ntest-zero")); + at.touch(at.plus_as_string("1\ntest-zero")); let ignored_opts = [ "--quoting-style=c", @@ -933,9 +933,9 @@ fn test_ls_zero() { fn test_ls_commas_trailing() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; - at.touch(&at.plus_as_string("test-commas-trailing-2")); + at.touch(at.plus_as_string("test-commas-trailing-2")); - at.touch(&at.plus_as_string("test-commas-trailing-1")); + at.touch(at.plus_as_string("test-commas-trailing-1")); at.append( "test-commas-trailing-1", &(0..2000) @@ -957,7 +957,7 @@ fn test_ls_commas_trailing() { fn test_ls_long() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; - at.touch(&at.plus_as_string("test-long")); + at.touch(at.plus_as_string("test-long")); for arg in LONG_ARGS { let result = scene.ucmd().arg(arg).arg("test-long").succeeds(); @@ -975,7 +975,7 @@ fn test_ls_long_format() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; at.mkdir(&at.plus_as_string("test-long-dir")); - at.touch(&at.plus_as_string("test-long-dir/test-long-file")); + at.touch(at.plus_as_string("test-long-dir/test-long-file")); at.mkdir(&at.plus_as_string("test-long-dir/test-long-dir")); for arg in LONG_ARGS { @@ -1231,9 +1231,9 @@ fn test_ls_long_symlink_color() { fn test_ls_long_total_size() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; - at.touch(&at.plus_as_string("test-long")); + at.touch(at.plus_as_string("test-long")); at.append("test-long", "1"); - at.touch(&at.plus_as_string("test-long2")); + at.touch(at.plus_as_string("test-long2")); at.append("test-long2", "2"); let expected_prints: HashMap<_, _> = if cfg!(unix) { @@ -1275,7 +1275,7 @@ fn test_ls_long_total_size() { fn test_ls_long_formats() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; - at.touch(&at.plus_as_string("test-long-formats")); + at.touch(at.plus_as_string("test-long-formats")); // Zero or one "." for indicating a file with security context @@ -1422,8 +1422,8 @@ fn test_ls_long_formats() { fn test_ls_oneline() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; - at.touch(&at.plus_as_string("test-oneline-1")); - at.touch(&at.plus_as_string("test-oneline-2")); + at.touch(at.plus_as_string("test-oneline-1")); + at.touch(at.plus_as_string("test-oneline-2")); // Bit of a weird situation: in the tests oneline and columns have the same output, // except on Windows. @@ -1443,7 +1443,7 @@ fn test_ls_deref() { let path_regexp = r"(.*)test-long.link -> (.*)test-long(.*)"; let re = Regex::new(path_regexp).unwrap(); - at.touch(&at.plus_as_string("test-long")); + at.touch(at.plus_as_string("test-long")); at.symlink_file("test-long", "test-long.link"); assert!(at.is_symlink("test-long.link")); @@ -1808,8 +1808,8 @@ fn test_ls_files_dirs() { at.mkdir("a/b"); at.mkdir("a/b/c"); at.mkdir("z"); - at.touch(&at.plus_as_string("a/a")); - at.touch(&at.plus_as_string("a/b/b")); + at.touch(at.plus_as_string("a/a")); + at.touch(at.plus_as_string("a/b/b")); scene.ucmd().arg("a").succeeds(); scene.ucmd().arg("a/a").succeeds(); @@ -1840,8 +1840,8 @@ fn test_ls_recursive() { at.mkdir("a/b"); at.mkdir("a/b/c"); at.mkdir("z"); - at.touch(&at.plus_as_string("a/a")); - at.touch(&at.plus_as_string("a/b/b")); + at.touch(at.plus_as_string("a/a")); + at.touch(at.plus_as_string("a/b/b")); scene.ucmd().arg("a").succeeds(); scene.ucmd().arg("a/a").succeeds(); @@ -1880,7 +1880,7 @@ fn test_ls_color() { .join("nested_file") .to_string_lossy() .to_string(); - at.touch(&nested_file); + at.touch(nested_file); at.touch("test-color"); let a_with_colors = "\x1b[1;34ma\x1b[0m"; @@ -1985,7 +1985,7 @@ fn test_ls_indicator_style() { at.mkdir("directory"); assert!(at.dir_exists("directory")); - at.touch(&at.plus_as_string("link-src")); + at.touch(at.plus_as_string("link-src")); at.symlink_file("link-src", "link-dest.link"); assert!(at.is_symlink("link-dest.link")); @@ -2077,7 +2077,7 @@ fn test_ls_indicator_style() { at.mkdir("directory"); assert!(at.dir_exists("directory")); - at.touch(&at.plus_as_string("link-src")); + at.touch(at.plus_as_string("link-src")); at.symlink_file("link-src", "link-dest.link"); assert!(at.is_symlink("link-dest.link")); diff --git a/tests/by-util/test_mv.rs b/tests/by-util/test_mv.rs index 93693279bc8..c673e56432a 100644 --- a/tests/by-util/test_mv.rs +++ b/tests/by-util/test_mv.rs @@ -55,7 +55,7 @@ fn test_mv_move_file_into_dir() { ucmd.arg(file).arg(dir).succeeds().no_stderr(); - assert!(at.file_exists(&format!("{dir}/{file}"))); + assert!(at.file_exists(format!("{dir}/{file}"))); } #[test] @@ -67,17 +67,17 @@ fn test_mv_move_file_between_dirs() { at.mkdir(dir1); at.mkdir(dir2); - at.touch(&format!("{dir1}/{file}")); + at.touch(format!("{dir1}/{file}")); - assert!(at.file_exists(&format!("{dir1}/{file}"))); + assert!(at.file_exists(format!("{dir1}/{file}"))); ucmd.arg(&format!("{dir1}/{file}")) .arg(dir2) .succeeds() .no_stderr(); - assert!(!at.file_exists(&format!("{dir1}/{file}"))); - assert!(at.file_exists(&format!("{dir2}/{file}"))); + assert!(!at.file_exists(format!("{dir1}/{file}"))); + assert!(at.file_exists(format!("{dir2}/{file}"))); } #[test] @@ -94,7 +94,7 @@ fn test_mv_strip_slashes() { scene.ucmd().arg(&source).arg(dir).fails(); - assert!(!at.file_exists(&format!("{dir}/{file}"))); + assert!(!at.file_exists(format!("{dir}/{file}"))); scene .ucmd() @@ -104,7 +104,7 @@ fn test_mv_strip_slashes() { .succeeds() .no_stderr(); - assert!(at.file_exists(&format!("{dir}/{file}"))); + assert!(at.file_exists(format!("{dir}/{file}"))); } #[test] @@ -124,8 +124,8 @@ fn test_mv_multiple_files() { .succeeds() .no_stderr(); - assert!(at.file_exists(&format!("{target_dir}/{file_a}"))); - assert!(at.file_exists(&format!("{target_dir}/{file_b}"))); + assert!(at.file_exists(format!("{target_dir}/{file_a}"))); + assert!(at.file_exists(format!("{target_dir}/{file_b}"))); } #[test] @@ -305,7 +305,7 @@ fn test_mv_simple_backup() { assert!(!at.file_exists(file_a)); assert!(at.file_exists(file_b)); - assert!(at.file_exists(&format!("{file_b}~"))); + assert!(at.file_exists(format!("{file_b}~"))); } #[test] @@ -324,7 +324,7 @@ fn test_mv_simple_backup_with_file_extension() { assert!(!at.file_exists(file_a)); assert!(at.file_exists(file_b)); - assert!(at.file_exists(&format!("{file_b}~"))); + assert!(at.file_exists(format!("{file_b}~"))); } #[test] @@ -339,7 +339,7 @@ fn test_mv_arg_backup_arg_first() { assert!(!at.file_exists(file_a)); assert!(at.file_exists(file_b)); - assert!(at.file_exists(&format!("{file_b}~"))); + assert!(at.file_exists(format!("{file_b}~"))); } #[test] @@ -360,7 +360,7 @@ fn test_mv_custom_backup_suffix() { assert!(!at.file_exists(file_a)); assert!(at.file_exists(file_b)); - assert!(at.file_exists(&format!("{file_b}{suffix}"))); + assert!(at.file_exists(format!("{file_b}{suffix}"))); } #[test] @@ -381,7 +381,7 @@ fn test_mv_custom_backup_suffix_hyphen_value() { assert!(!at.file_exists(file_a)); assert!(at.file_exists(file_b)); - assert!(at.file_exists(&format!("{file_b}{suffix}"))); + assert!(at.file_exists(format!("{file_b}{suffix}"))); } #[test] @@ -401,7 +401,7 @@ fn test_mv_custom_backup_suffix_via_env() { assert!(!at.file_exists(file_a)); assert!(at.file_exists(file_b)); - assert!(at.file_exists(&format!("{file_b}{suffix}"))); + assert!(at.file_exists(format!("{file_b}{suffix}"))); } #[test] @@ -420,7 +420,7 @@ fn test_mv_backup_numbered_with_t() { assert!(!at.file_exists(file_a)); assert!(at.file_exists(file_b)); - assert!(at.file_exists(&format!("{file_b}.~1~"))); + assert!(at.file_exists(format!("{file_b}.~1~"))); } #[test] @@ -439,7 +439,7 @@ fn test_mv_backup_numbered() { assert!(!at.file_exists(file_a)); assert!(at.file_exists(file_b)); - assert!(at.file_exists(&format!("{file_b}.~1~"))); + assert!(at.file_exists(format!("{file_b}.~1~"))); } #[test] @@ -458,7 +458,7 @@ fn test_mv_backup_existing() { assert!(!at.file_exists(file_a)); assert!(at.file_exists(file_b)); - assert!(at.file_exists(&format!("{file_b}~"))); + assert!(at.file_exists(format!("{file_b}~"))); } #[test] @@ -477,7 +477,7 @@ fn test_mv_backup_nil() { assert!(!at.file_exists(file_a)); assert!(at.file_exists(file_b)); - assert!(at.file_exists(&format!("{file_b}~"))); + assert!(at.file_exists(format!("{file_b}~"))); } #[test] @@ -498,7 +498,7 @@ fn test_mv_numbered_if_existing_backup_existing() { assert!(at.file_exists(file_b)); assert!(at.file_exists(file_b_backup)); - assert!(at.file_exists(&format!("{file_b}.~2~"))); + assert!(at.file_exists(format!("{file_b}.~2~"))); } #[test] @@ -519,7 +519,7 @@ fn test_mv_numbered_if_existing_backup_nil() { assert!(at.file_exists(file_b)); assert!(at.file_exists(file_b_backup)); - assert!(at.file_exists(&format!("{file_b}.~2~"))); + assert!(at.file_exists(format!("{file_b}.~2~"))); } #[test] @@ -538,7 +538,7 @@ fn test_mv_backup_simple() { assert!(!at.file_exists(file_a)); assert!(at.file_exists(file_b)); - assert!(at.file_exists(&format!("{file_b}~"))); + assert!(at.file_exists(format!("{file_b}~"))); } #[test] @@ -557,7 +557,7 @@ fn test_mv_backup_never() { assert!(!at.file_exists(file_a)); assert!(at.file_exists(file_b)); - assert!(at.file_exists(&format!("{file_b}~"))); + assert!(at.file_exists(format!("{file_b}~"))); } #[test] @@ -576,7 +576,7 @@ fn test_mv_backup_none() { assert!(!at.file_exists(file_a)); assert!(at.file_exists(file_b)); - assert!(!at.file_exists(&format!("{file_b}~"))); + assert!(!at.file_exists(format!("{file_b}~"))); } #[test] @@ -595,7 +595,7 @@ fn test_mv_backup_off() { assert!(!at.file_exists(file_a)); assert!(at.file_exists(file_b)); - assert!(!at.file_exists(&format!("{file_b}~"))); + assert!(!at.file_exists(format!("{file_b}~"))); } #[test] @@ -660,8 +660,8 @@ fn test_mv_target_dir() { assert!(!at.file_exists(file_a)); assert!(!at.file_exists(file_b)); - assert!(at.file_exists(&format!("{dir}/{file_a}"))); - assert!(at.file_exists(&format!("{dir}/{file_b}"))); + assert!(at.file_exists(format!("{dir}/{file_a}"))); + assert!(at.file_exists(format!("{dir}/{file_b}"))); } #[test] @@ -675,7 +675,7 @@ fn test_mv_target_dir_single_source() { ucmd.arg("-t").arg(dir).arg(file).succeeds().no_stderr(); assert!(!at.file_exists(file)); - assert!(at.file_exists(&format!("{dir}/{file}"))); + assert!(at.file_exists(format!("{dir}/{file}"))); } #[test] diff --git a/tests/by-util/test_realpath.rs b/tests/by-util/test_realpath.rs index 2993d8dbb7e..691ad29a21b 100644 --- a/tests/by-util/test_realpath.rs +++ b/tests/by-util/test_realpath.rs @@ -192,7 +192,7 @@ fn test_realpath_existing() { ucmd.arg("-e") .arg(".") .succeeds() - .stdout_only(at.plus_as_string(&format!("{}\n", at.root_dir_resolved()))); + .stdout_only(at.plus_as_string(format!("{}\n", at.root_dir_resolved()))); } #[test] diff --git a/tests/common/util.rs b/tests/common/util.rs index ed4ecf8e9ce..b9600e5b686 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -746,14 +746,14 @@ impl AtPath { self.subdir.to_str().unwrap().to_owned() } - pub fn plus(&self, name: &str) -> PathBuf { + pub fn plus>(&self, name: P) -> PathBuf { let mut pathbuf = self.subdir.clone(); pathbuf.push(name); pathbuf } - pub fn plus_as_string(&self, name: &str) -> String { - String::from(self.plus(name).to_str().unwrap()) + pub fn plus_as_string>(&self, name: P) -> String { + self.plus(name).display().to_string() } fn minus(&self, name: &str) -> PathBuf { @@ -876,7 +876,8 @@ impl AtPath { fs::remove_dir(self.plus(dir)).unwrap(); } - pub fn mkdir(&self, dir: &str) { + pub fn mkdir>(&self, dir: P) { + let dir = dir.as_ref(); log_info("mkdir", self.plus_as_string(dir)); fs::create_dir(self.plus(dir)).unwrap(); } @@ -893,7 +894,8 @@ impl AtPath { } } - pub fn touch(&self, file: &str) { + pub fn touch>(&self, file: P) { + let file = file.as_ref(); log_info("touch", self.plus_as_string(file)); File::create(self.plus(file)).unwrap(); } @@ -1016,7 +1018,7 @@ impl AtPath { } } - pub fn file_exists(&self, path: &str) -> bool { + pub fn file_exists>(&self, path: P) -> bool { match fs::metadata(self.plus(path)) { Ok(m) => m.is_file(), Err(_) => false, From f928d2984dc1d4987016b89dd388436485b9e394 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 22 Feb 2023 21:54:31 +0100 Subject: [PATCH 008/700] Freebsd pkg has been remamed to rust-coreutils https://cgit.FreeBSD.org/ports/commit/?id=8014b050aa2f5d3e016d48c98865eabb0e6b12fe --- docs/src/installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/installation.md b/docs/src/installation.md index 063935ed68d..0bd302a36e3 100644 --- a/docs/src/installation.md +++ b/docs/src/installation.md @@ -111,7 +111,7 @@ port install coreutils-uutils [![FreeBSD port](https://repology.org/badge/version-for-repo/freebsd/uutils-coreutils.svg)](https://repology.org/project/uutils-coreutils/versions) ```sh -pkg install uutils +pkg install rust-coreutils ``` ## Windows From 3d2f3fc5b155a1ba9161e921ffbdf763f671712f Mon Sep 17 00:00:00 2001 From: ZauJulio Date: Wed, 22 Feb 2023 20:22:16 -0300 Subject: [PATCH 009/700] mktemp: add test to . in template prefix --- tests/by-util/test_mktemp.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/by-util/test_mktemp.rs b/tests/by-util/test_mktemp.rs index d6926c41b26..6d4a7fb6f07 100644 --- a/tests/by-util/test_mktemp.rs +++ b/tests/by-util/test_mktemp.rs @@ -21,6 +21,7 @@ static TEST_TEMPLATE6: &str = "tempXXXlate"; // spell-checker:disable-line static TEST_TEMPLATE7: &str = "XXXtemplate"; // spell-checker:disable-line #[cfg(unix)] static TEST_TEMPLATE8: &str = "tempXXXl/ate"; +static TEST_TEMPLATE9: &str = "a.XXXX"; #[cfg(windows)] static TEST_TEMPLATE8: &str = "tempXXXl\\ate"; @@ -569,6 +570,14 @@ fn test_template_path_separator() { )); } +/// Test that a prefix with a point is valid. +#[test] +fn test_prefix_template_separator() { + new_ucmd!() + .args(&["-t", TEST_TEMPLATE9]) + .succeeds(); +} + /// Test that a suffix with a path separator is invalid. #[test] fn test_suffix_path_separator() { From e37e5ad9159ca4ed8154076b16f3b883942e9fed Mon Sep 17 00:00:00 2001 From: curtain Date: Thu, 23 Feb 2023 09:43:54 +0800 Subject: [PATCH 010/700] basename: fix rustfmt problem --- src/uu/basename/src/basename.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/basename/src/basename.rs b/src/uu/basename/src/basename.rs index 48f00f4b886..ed7faee6566 100644 --- a/src/uu/basename/src/basename.rs +++ b/src/uu/basename/src/basename.rs @@ -11,7 +11,7 @@ use clap::{crate_version, Arg, ArgAction, Command}; use std::path::{is_separator, PathBuf}; use uucore::display::Quotable; use uucore::error::{UResult, UUsageError}; -use uucore::{format_usage, help_usage, help_about}; +use uucore::{format_usage, help_about, help_usage}; static ABOUT: &str = help_about!("basename.md"); From dc1fd027a6ca0f609cf9ba3eb63c6bc75ec9c011 Mon Sep 17 00:00:00 2001 From: ZauJulio Date: Sat, 25 Feb 2023 18:35:35 -0300 Subject: [PATCH 011/700] mktemp: fix fmt of test_prefix_template_separator --- tests/by-util/test_mktemp.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/by-util/test_mktemp.rs b/tests/by-util/test_mktemp.rs index dc09204b612..081e4da1ba4 100644 --- a/tests/by-util/test_mktemp.rs +++ b/tests/by-util/test_mktemp.rs @@ -575,9 +575,7 @@ fn test_template_path_separator() { /// Test that a prefix with a point is valid. #[test] fn test_prefix_template_separator() { - new_ucmd!() - .args(&["-t", TEST_TEMPLATE9]) - .succeeds(); + new_ucmd!().args(&["-t", TEST_TEMPLATE9]).succeeds(); } /// Test that a suffix with a path separator is invalid. From a87bc9f9291fe707900f3434a8bdc7803366eb44 Mon Sep 17 00:00:00 2001 From: ZauJulio Date: Mon, 27 Feb 2023 01:13:16 -0300 Subject: [PATCH 012/700] mktemp: remove unnecessary convertion for path -> str just use prefix_from_template --- src/uu/mktemp/src/mktemp.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/uu/mktemp/src/mktemp.rs b/src/uu/mktemp/src/mktemp.rs index 1764b8e84dd..5d430895804 100644 --- a/src/uu/mktemp/src/mktemp.rs +++ b/src/uu/mktemp/src/mktemp.rs @@ -281,11 +281,7 @@ impl Params { .join(prefix_from_template) .display() .to_string(); - - // Check that the prefix is valid. - let prefix_of_template = Path::new(prefix_from_template).to_string_lossy(); - - if options.treat_as_template && prefix_of_template.contains(MAIN_SEPARATOR) { + if options.treat_as_template && prefix_from_template.contains(MAIN_SEPARATOR) { return Err(MkTempError::PrefixContainsDirSeparator(options.template)); } if tmpdir.is_some() && Path::new(prefix_from_template).is_absolute() { From 68f75af678fb2115681c7da4d4a183bed0ab70de Mon Sep 17 00:00:00 2001 From: Joining7943 <111500881+Joining7943@users.noreply.github.com> Date: Sun, 26 Feb 2023 07:32:07 +0100 Subject: [PATCH 013/700] sleep: Fix duration parsing of multiple inputs when duration overflow should saturate and not panic --- src/uu/sleep/src/sleep.rs | 2 +- tests/by-util/test_sleep.rs | 68 +++++++++++++++++++++++++------------ 2 files changed, 48 insertions(+), 22 deletions(-) diff --git a/src/uu/sleep/src/sleep.rs b/src/uu/sleep/src/sleep.rs index 517fda098c7..dde25220400 100644 --- a/src/uu/sleep/src/sleep.rs +++ b/src/uu/sleep/src/sleep.rs @@ -69,7 +69,7 @@ fn sleep(args: &[&str]) -> UResult<()> { Duration::new(0, 0) } }); - let sleep_dur = intervals.fold(Duration::new(0, 0), |acc, n| acc + n); + let sleep_dur = intervals.fold(Duration::ZERO, |acc, n| acc.saturating_add(n)); if arg_error { return Err(UUsageError::new(1, "")); }; diff --git a/tests/by-util/test_sleep.rs b/tests/by-util/test_sleep.rs index b6b573508c9..5f538900f8b 100644 --- a/tests/by-util/test_sleep.rs +++ b/tests/by-util/test_sleep.rs @@ -129,29 +129,55 @@ fn test_sleep_wrong_time() { new_ucmd!().args(&["0.1s", "abc"]).fails(); } -// TODO These tests would obviously block for a very long time. We -// only want to verify that there is no error here, so we could just -// figure out a way to terminate the child process after a short -// period of time. - -// #[test] -#[allow(dead_code)] -fn test_dont_overflow() { - new_ucmd!() - .arg("9223372036854775808d") - .succeeds() - .no_stderr() - .no_stdout(); +#[test] +fn test_sleep_when_single_input_exceeds_max_duration_then_no_error() { + let mut child = new_ucmd!() + .arg(format!("{}", u64::MAX as u128 + 1)) + .timeout(Duration::from_secs(10)) + .run_no_wait(); + + #[cfg(unix)] + child + .delay(100) + .kill() + .make_assertion() + .with_current_output() + .signal_is(9) // make sure it was us who terminated the process + .no_output(); + #[cfg(windows)] + child + .delay(100) + .kill() + .make_assertion() + .with_current_output() + .failure() + .no_output(); } -// #[test] -#[allow(dead_code)] -fn test_sum_overflow() { - new_ucmd!() - .args(&["100000000000000d", "100000000000000d", "100000000000000d"]) - .succeeds() - .no_stderr() - .no_stdout(); +#[test] +fn test_sleep_when_multiple_inputs_exceed_max_duration_then_no_error() { + let mut child = new_ucmd!() + .arg(format!("{}", u64::MAX)) + .arg("1") + .timeout(Duration::from_secs(10)) + .run_no_wait(); + + #[cfg(unix)] + child + .delay(100) + .kill() + .make_assertion() + .with_current_output() + .signal_is(9) // make sure it was us who terminated the process + .no_output(); + #[cfg(windows)] + child + .delay(100) + .kill() + .make_assertion() + .with_current_output() + .failure() + .no_output(); } #[test] From 9b4fb0cb6eb46b3e3cba1d318e317b821dfa9328 Mon Sep 17 00:00:00 2001 From: ZauJulio Date: Tue, 28 Feb 2023 12:38:18 -0300 Subject: [PATCH 014/700] mktemp: add tests UNIX and POSIX to check path in prefix --- tests/by-util/test_mktemp.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/by-util/test_mktemp.rs b/tests/by-util/test_mktemp.rs index 081e4da1ba4..7c3d621506b 100644 --- a/tests/by-util/test_mktemp.rs +++ b/tests/by-util/test_mktemp.rs @@ -578,6 +578,26 @@ fn test_prefix_template_separator() { new_ucmd!().args(&["-t", TEST_TEMPLATE9]).succeeds(); } +#[test] +fn test_prefix_template_with_path_separator() { + #[cfg(not(windows))] + new_ucmd!() + .args(&["-t", "a/XXX"]) + .fails() + .stderr_only(format!( + "mktemp: invalid template, {}, contains directory separator\n", + "a/XXX".quote() + )); + #[cfg(windows)] + new_ucmd!() + .args(&["-t", r"a\XXX"]) + .fails() + .stderr_only(format!( + "mktemp: invalid template, {}, contains directory separator\n", + r"a\XXX".quote() + )); +} + /// Test that a suffix with a path separator is invalid. #[test] fn test_suffix_path_separator() { From b83c30b12e88a8549e2f9367d80664eb73090187 Mon Sep 17 00:00:00 2001 From: Benjamin Bara Date: Sat, 11 Feb 2023 21:14:12 +0100 Subject: [PATCH 015/700] tail: improve GNU compatibility --- src/uu/tail/src/args.rs | 23 +++- src/uu/tail/src/parse.rs | 169 +++++++++++--------------- tests/by-util/test_tail.rs | 236 +++++++++++++++++++++++++++++++++++++ 3 files changed, 324 insertions(+), 104 deletions(-) diff --git a/src/uu/tail/src/args.rs b/src/uu/tail/src/args.rs index 6cb77757c9a..aa4b1c86744 100644 --- a/src/uu/tail/src/args.rs +++ b/src/uu/tail/src/args.rs @@ -64,7 +64,12 @@ impl FilterMode { let mode = if let Some(arg) = matches.get_one::(options::BYTES) { match parse_num(arg) { Ok(signum) => Self::Bytes(signum), - Err(e) => return Err(UUsageError::new(1, format!("invalid number of bytes: {e}"))), + Err(e) => { + return Err(USimpleError::new( + 1, + format!("invalid number of bytes: {e}"), + )) + } } } else if let Some(arg) = matches.get_one::(options::LINES) { match parse_num(arg) { @@ -72,7 +77,12 @@ impl FilterMode { let delimiter = if zero_term { 0 } else { b'\n' }; Self::Lines(signum, delimiter) } - Err(e) => return Err(UUsageError::new(1, format!("invalid number of lines: {e}"))), + Err(e) => { + return Err(USimpleError::new( + 1, + format!("invalid number of lines: {e}"), + )) + } } } else if zero_term { Self::default_zero() @@ -307,14 +317,19 @@ pub fn arg_iterate<'a>( if let Some(s) = second.to_str() { match parse::parse_obsolete(s) { Some(Ok(iter)) => Ok(Box::new(vec![first].into_iter().chain(iter).chain(args))), - Some(Err(e)) => Err(UUsageError::new( + Some(Err(e)) => Err(USimpleError::new( 1, match e { - parse::ParseError::Syntax => format!("bad argument format: {}", s.quote()), parse::ParseError::Overflow => format!( "invalid argument: {} Value too large for defined datatype", s.quote() ), + parse::ParseError::Context => { + format!( + "option used in invalid context -- {}", + s.chars().nth(1).unwrap_or_default() + ) + } }, )), None => Ok(Box::new(vec![first, second].into_iter().chain(args))), diff --git a/src/uu/tail/src/parse.rs b/src/uu/tail/src/parse.rs index 2129d8e299f..15c770bc5ce 100644 --- a/src/uu/tail/src/parse.rs +++ b/src/uu/tail/src/parse.rs @@ -7,92 +7,67 @@ use std::ffi::OsString; #[derive(PartialEq, Eq, Debug)] pub enum ParseError { - Syntax, Overflow, + Context, } /// Parses obsolete syntax -/// tail -NUM\[kmzv\] // spell-checker:disable-line +/// tail -\[NUM\]\[bl\]\[f\] and tail +\[NUM\]\[bcl\]\[f\] // spell-checker:disable-line pub fn parse_obsolete(src: &str) -> Option, ParseError>> { - let mut chars = src.char_indices(); - if let Some((_, '-')) = chars.next() { - let mut num_end = 0usize; - let mut has_num = false; - let mut last_char = 0 as char; - for (n, c) in &mut chars { - if c.is_ascii_digit() { - has_num = true; - num_end = n; - } else { - last_char = c; - break; - } + let mut chars = src.chars(); + let sign = chars.next()?; + if sign != '+' && sign != '-' { + return None; + } + + let numbers: String = chars.clone().take_while(|&c| c.is_ascii_digit()).collect(); + let has_num = !numbers.is_empty(); + let num: usize = if has_num { + if let Ok(num) = numbers.parse() { + num + } else { + return Some(Err(ParseError::Overflow)); } - if has_num { - match src[1..=num_end].parse::() { - Ok(num) => { - let mut quiet = false; - let mut verbose = false; - let mut zero_terminated = false; - let mut multiplier = None; - let mut c = last_char; - loop { - // not that here, we only match lower case 'k', 'c', and 'm' - match c { - // we want to preserve order - // this also saves us 1 heap allocation - 'q' => { - quiet = true; - verbose = false; - } - 'v' => { - verbose = true; - quiet = false; - } - 'z' => zero_terminated = true, - 'c' => multiplier = Some(1), - 'b' => multiplier = Some(512), - 'k' => multiplier = Some(1024), - 'm' => multiplier = Some(1024 * 1024), - '\0' => {} - _ => return Some(Err(ParseError::Syntax)), - } - if let Some((_, next)) = chars.next() { - c = next; - } else { - break; - } - } - let mut options = Vec::new(); - if quiet { - options.push(OsString::from("-q")); - } - if verbose { - options.push(OsString::from("-v")); - } - if zero_terminated { - options.push(OsString::from("-z")); - } - if let Some(n) = multiplier { - options.push(OsString::from("-c")); - let num = match num.checked_mul(n) { - Some(n) => n, - None => return Some(Err(ParseError::Overflow)), - }; - options.push(OsString::from(format!("{num}"))); - } else { - options.push(OsString::from("-n")); - options.push(OsString::from(format!("{num}"))); - } - Some(Ok(options.into_iter())) - } - Err(_) => Some(Err(ParseError::Overflow)), - } + } else { + 10 + }; + + let mut follow = false; + let mut mode = None; + let mut first_char = true; + for char in chars.skip_while(|&c| c.is_ascii_digit()) { + if sign == '-' && char == 'c' && !has_num { + // special case: -c should be handled by clap (is ambiguous) + return None; + } else if char == 'f' { + follow = true; + } else if first_char && (char == 'b' || char == 'c' || char == 'l') { + mode = Some(char); + } else if has_num && sign == '-' { + return Some(Err(ParseError::Context)); } else { - None + return None; } + first_char = false; + } + + let mut options = Vec::new(); + if follow { + options.push(OsString::from("-f")); + } + let mode = mode.unwrap_or('l'); + if mode == 'b' || mode == 'c' { + options.push(OsString::from("-c")); + let n = if mode == 'b' { 512 } else { 1 }; + let num = match num.checked_mul(n) { + Some(n) => n, + None => return Some(Err(ParseError::Overflow)), + }; + options.push(OsString::from(format!("{sign}{num}"))); } else { - None + options.push(OsString::from("-n")); + options.push(OsString::from(format!("{sign}{num}"))); } + Some(Ok(options.into_iter())) } #[cfg(test)] @@ -113,40 +88,35 @@ mod tests { } #[test] fn test_parse_numbers_obsolete() { - assert_eq!(obsolete("-5"), obsolete_result(&["-n", "5"])); - assert_eq!(obsolete("-100"), obsolete_result(&["-n", "100"])); - assert_eq!(obsolete("-5m"), obsolete_result(&["-c", "5242880"])); - assert_eq!(obsolete("-1k"), obsolete_result(&["-c", "1024"])); - assert_eq!(obsolete("-2b"), obsolete_result(&["-c", "1024"])); - assert_eq!(obsolete("-1mmk"), obsolete_result(&["-c", "1024"])); - assert_eq!(obsolete("-1vz"), obsolete_result(&["-v", "-z", "-n", "1"])); - assert_eq!( - obsolete("-1vzqvq"), // spell-checker:disable-line - obsolete_result(&["-q", "-z", "-n", "1"]) - ); - assert_eq!(obsolete("-1vzc"), obsolete_result(&["-v", "-z", "-c", "1"])); - assert_eq!( - obsolete("-105kzm"), - obsolete_result(&["-z", "-c", "110100480"]) - ); + assert_eq!(obsolete("+2c"), obsolete_result(&["-c", "+2"])); + assert_eq!(obsolete("-5"), obsolete_result(&["-n", "-5"])); + assert_eq!(obsolete("-100"), obsolete_result(&["-n", "-100"])); + assert_eq!(obsolete("-2b"), obsolete_result(&["-c", "-1024"])); } #[test] fn test_parse_errors_obsolete() { - assert_eq!(obsolete("-5n"), Some(Err(ParseError::Syntax))); - assert_eq!(obsolete("-5c5"), Some(Err(ParseError::Syntax))); + assert_eq!(obsolete("-5n"), Some(Err(ParseError::Context))); + assert_eq!(obsolete("-5c5"), Some(Err(ParseError::Context))); + assert_eq!(obsolete("-1vzc"), Some(Err(ParseError::Context))); + assert_eq!(obsolete("-5m"), Some(Err(ParseError::Context))); + assert_eq!(obsolete("-1k"), Some(Err(ParseError::Context))); + assert_eq!(obsolete("-1mmk"), Some(Err(ParseError::Context))); + assert_eq!(obsolete("-105kzm"), Some(Err(ParseError::Context))); + assert_eq!(obsolete("-1vz"), Some(Err(ParseError::Context))); + assert_eq!( + obsolete("-1vzqvq"), // spell-checker:disable-line + Some(Err(ParseError::Context)) + ); } #[test] fn test_parse_obsolete_no_match() { assert_eq!(obsolete("-k"), None); assert_eq!(obsolete("asd"), None); + assert_eq!(obsolete("-cc"), None); } #[test] #[cfg(target_pointer_width = "64")] fn test_parse_obsolete_overflow_x64() { - assert_eq!( - obsolete("-1000000000000000m"), - Some(Err(ParseError::Overflow)) - ); assert_eq!( obsolete("-10000000000000000000000"), Some(Err(ParseError::Overflow)) @@ -156,6 +126,5 @@ mod tests { #[cfg(target_pointer_width = "32")] fn test_parse_obsolete_overflow_x32() { assert_eq!(obsolete("-42949672960"), Some(Err(ParseError::Overflow))); - assert_eq!(obsolete("-42949672k"), Some(Err(ParseError::Overflow))); } } diff --git a/tests/by-util/test_tail.rs b/tests/by-util/test_tail.rs index b2ec0e7bd05..4644cbc029c 100644 --- a/tests/by-util/test_tail.rs +++ b/tests/by-util/test_tail.rs @@ -4475,3 +4475,239 @@ fn test_args_sleep_interval_when_illegal_argument_then_usage_error(#[case] sleep .usage_error(format!("invalid number of seconds: '{sleep_interval}'")) .code_is(1); } + +#[test] +fn test_gnu_args_plus_c() { + let scene = TestScenario::new(util_name!()); + + // obs-plus-c1 + scene + .ucmd() + .arg("+2c") + .pipe_in("abcd") + .succeeds() + .stdout_only("bcd"); + // obs-plus-c2 + scene + .ucmd() + .arg("+8c") + .pipe_in("abcd") + .succeeds() + .stdout_only(""); + // obs-plus-x1: same as +10c + scene + .ucmd() + .arg("+c") + .pipe_in(format!("x{}z", "y".repeat(10))) + .succeeds() + .stdout_only("yyz"); +} + +#[test] +fn test_gnu_args_c() { + let scene = TestScenario::new(util_name!()); + + // obs-c3 + scene + .ucmd() + .arg("-1c") + .pipe_in("abcd") + .succeeds() + .stdout_only("d"); + // obs-c4 + scene + .ucmd() + .arg("-9c") + .pipe_in("abcd") + .succeeds() + .stdout_only("abcd"); + // obs-c5 + scene + .ucmd() + .arg("-12c") + .pipe_in(format!("x{}z", "y".repeat(12))) + .succeeds() + .stdout_only(&format!("{}z", "y".repeat(11))); +} + +#[test] +fn test_gnu_args_l() { + let scene = TestScenario::new(util_name!()); + + // obs-l1 + scene + .ucmd() + .arg("-1l") + .pipe_in("x") + .succeeds() + .stdout_only("x"); + // obs-l2 + scene + .ucmd() + .arg("-1l") + .pipe_in("x\ny\n") + .succeeds() + .stdout_only("y\n"); + // obs-l3 + scene + .ucmd() + .arg("-1l") + .pipe_in("x\ny") + .succeeds() + .stdout_only("y"); + // obs-l: same as -10l + scene + .ucmd() + .arg("-l") + .pipe_in(format!("x{}z", "y\n".repeat(10))) + .succeeds() + .stdout_only(&format!("{}z", "y\n".repeat(9))); +} + +#[test] +fn test_gnu_args_plus_l() { + let scene = TestScenario::new(util_name!()); + + // obs-plus-l4 + scene + .ucmd() + .arg("+1l") + .pipe_in("x\ny\n") + .succeeds() + .stdout_only("x\ny\n"); + // ops-plus-l5 + scene + .ucmd() + .arg("+2l") + .pipe_in("x\ny\n") + .succeeds() + .stdout_only("y\n"); + // obs-plus-x2: same as +10l + scene + .ucmd() + .arg("+l") + .pipe_in(format!("x\n{}z", "y\n".repeat(10))) + .succeeds() + .stdout_only("y\ny\nz"); +} + +#[test] +fn test_gnu_args_number() { + let scene = TestScenario::new(util_name!()); + + // obs-1 + scene + .ucmd() + .arg("-1") + .pipe_in("x") + .succeeds() + .stdout_only("x"); + // obs-2 + scene + .ucmd() + .arg("-1") + .pipe_in("x\ny\n") + .succeeds() + .stdout_only("y\n"); + // obs-3 + scene + .ucmd() + .arg("-1") + .pipe_in("x\ny") + .succeeds() + .stdout_only("y"); +} + +#[test] +fn test_gnu_args_plus_number() { + let scene = TestScenario::new(util_name!()); + + // obs-plus-4 + scene + .ucmd() + .arg("+1") + .pipe_in("x\ny\n") + .succeeds() + .stdout_only("x\ny\n"); + // ops-plus-5 + scene + .ucmd() + .arg("+2") + .pipe_in("x\ny\n") + .succeeds() + .stdout_only("y\n"); +} + +#[test] +fn test_gnu_args_b() { + let scene = TestScenario::new(util_name!()); + + // obs-b + scene + .ucmd() + .arg("-b") + .pipe_in("x\n".repeat(512 * 10 / 2 + 1)) + .succeeds() + .stdout_only(&"x\n".repeat(512 * 10 / 2)); +} + +#[test] +fn test_gnu_args_err() { + let scene = TestScenario::new(util_name!()); + + // err-1 + scene + .ucmd() + .arg("+cl") + .fails() + .no_stdout() + .stderr_is("tail: cannot open '+cl' for reading: No such file or directory\n") + .code_is(1); + // err-2 + scene + .ucmd() + .arg("-cl") + .fails() + .no_stdout() + .stderr_is("tail: invalid number of bytes: 'l'\n") + .code_is(1); + // err-3 + scene + .ucmd() + .arg("+2cz") + .fails() + .no_stdout() + .stderr_is("tail: cannot open '+2cz' for reading: No such file or directory\n") + .code_is(1); + // err-4 + scene + .ucmd() + .arg("-2cX") + .fails() + .no_stdout() + .stderr_is("tail: option used in invalid context -- 2\n") + .code_is(1); + // err-5 + scene + .ucmd() + .arg("-c99999999999999999999") + .fails() + .no_stdout() + .stderr_is("tail: invalid number of bytes: '99999999999999999999'\n") + .code_is(1); + // err-6 + scene + .ucmd() + .arg("-c --") + .fails() + .no_stdout() + .stderr_is("tail: invalid number of bytes: '-'\n") + .code_is(1); + scene + .ucmd() + .arg("-5cz") + .fails() + .no_stdout() + .stderr_is("tail: option used in invalid context -- 5\n") + .code_is(1); +} From dc34e89d5044f545740b14fd8d3a092b1338ab11 Mon Sep 17 00:00:00 2001 From: Benjamin Bara Date: Thu, 16 Feb 2023 21:16:21 +0100 Subject: [PATCH 016/700] tail: skip clap for obsolete args --- src/uu/tail/src/args.rs | 124 +++++++++++++++++++++--------- src/uu/tail/src/parse.rs | 153 ++++++++++++++++++++----------------- tests/by-util/test_tail.rs | 108 +++++++++++++++++++++++++- 3 files changed, 278 insertions(+), 107 deletions(-) diff --git a/src/uu/tail/src/args.rs b/src/uu/tail/src/args.rs index aa4b1c86744..f18f99d1b58 100644 --- a/src/uu/tail/src/args.rs +++ b/src/uu/tail/src/args.rs @@ -13,7 +13,6 @@ use fundu::DurationParser; use is_terminal::IsTerminal; use same_file::Handle; use std::collections::VecDeque; -use std::ffi::OsString; use std::time::Duration; use uucore::error::{UResult, USimpleError, UUsageError}; use uucore::parse_size::{parse_size, ParseSizeError}; @@ -59,6 +58,19 @@ pub enum FilterMode { } impl FilterMode { + fn from_obsolete_args(args: &parse::ObsoleteArgs) -> Self { + let signum = if args.plus { + Signum::Positive(args.num) + } else { + Signum::Negative(args.num) + }; + if args.lines { + Self::Lines(signum, b'\n') + } else { + Self::Bytes(signum) + } + } + fn from(matches: &ArgMatches) -> UResult { let zero_term = matches.get_flag(options::ZERO_TERM); let mode = if let Some(arg) = matches.get_one::(options::BYTES) { @@ -132,6 +144,29 @@ pub struct Settings { } impl Settings { + pub fn from_obsolete_args(args: &parse::ObsoleteArgs, name: Option<&str>) -> Self { + let mut settings: Self = Self { + sleep_sec: Duration::from_secs_f32(1.0), + max_unchanged_stats: 5, + ..Default::default() + }; + if args.follow { + settings.follow = if name.is_some() { + Some(FollowMode::Name) + } else { + Some(FollowMode::Descriptor) + }; + } + settings.mode = FilterMode::from_obsolete_args(args); + let input = if let Some(name) = name { + Input::from(name.to_string()) + } else { + Input::default() + }; + settings.inputs.push_back(input); + settings + } + pub fn from(matches: &clap::ArgMatches) -> UResult { let mut settings: Self = Self { sleep_sec: Duration::from_secs_f32(1.0), @@ -308,37 +343,24 @@ impl Settings { } } -pub fn arg_iterate<'a>( - mut args: impl uucore::Args + 'a, -) -> UResult + 'a>> { - // argv[0] is always present - let first = args.next().unwrap(); - if let Some(second) = args.next() { - if let Some(s) = second.to_str() { - match parse::parse_obsolete(s) { - Some(Ok(iter)) => Ok(Box::new(vec![first].into_iter().chain(iter).chain(args))), - Some(Err(e)) => Err(USimpleError::new( - 1, - match e { - parse::ParseError::Overflow => format!( - "invalid argument: {} Value too large for defined datatype", - s.quote() - ), - parse::ParseError::Context => { - format!( - "option used in invalid context -- {}", - s.chars().nth(1).unwrap_or_default() - ) - } - }, - )), - None => Ok(Box::new(vec![first, second].into_iter().chain(args))), - } - } else { - Err(UUsageError::new(1, "bad argument encoding".to_owned())) - } - } else { - Ok(Box::new(vec![first].into_iter())) +pub fn parse_obsolete(args: &str) -> UResult> { + match parse::parse_obsolete(args) { + Some(Ok(args)) => Ok(Some(args)), + None => Ok(None), + Some(Err(e)) => Err(USimpleError::new( + 1, + match e { + parse::ParseError::OutOfRange => format!( + "invalid number: {}: Numerical result out of range", + args.quote() + ), + parse::ParseError::Overflow => format!("invalid number: {}", args.quote()), + parse::ParseError::Context => format!( + "option used in invalid context -- {}", + args.chars().nth(1).unwrap_or_default() + ), + }, + )), } } @@ -366,9 +388,29 @@ fn parse_num(src: &str) -> Result { }) } -pub fn parse_args(args: impl uucore::Args) -> UResult { - let matches = uu_app().try_get_matches_from(arg_iterate(args)?)?; - Settings::from(&matches) +pub fn parse_args(mut args: impl uucore::Args) -> UResult { + let first = args.next().unwrap(); + let second = match args.next() { + Some(second) => second, + None => return Settings::from(&uu_app().try_get_matches_from(vec![first])?), + }; + let second_str = match second.to_str() { + Some(second_str) => second_str, + None => { + let second_string = second.to_string_lossy(); + return Err(USimpleError::new( + 1, + format!("bad argument encoding: '{second_string}'"), + )); + } + }; + match parse_obsolete(second_str)? { + Some(obsolete_args) => Ok(Settings::from_obsolete_args(&obsolete_args, args.next())), + None => { + let args = vec![first, second].into_iter().chain(args); + Settings::from(&uu_app().try_get_matches_from(args)?) + } + } } pub fn uu_app() -> Command { @@ -497,6 +539,8 @@ pub fn uu_app() -> Command { #[cfg(test)] mod tests { + use crate::parse::ObsoleteArgs; + use super::*; #[test] @@ -528,4 +572,14 @@ mod tests { assert!(result.is_ok()); assert_eq!(result.unwrap(), Signum::Negative(1)); } + + #[test] + fn test_parse_obsolete_settings_f() { + let args = ObsoleteArgs { follow: true, ..Default::default() }; + let result = Settings::from_obsolete_args(&args, None); + assert_eq!(result.follow, Some(FollowMode::Descriptor)); + + let result = Settings::from_obsolete_args(&args, Some("test".into())); + assert_eq!(result.follow, Some(FollowMode::Name)); + } } diff --git a/src/uu/tail/src/parse.rs b/src/uu/tail/src/parse.rs index 15c770bc5ce..2f4ebb62ebb 100644 --- a/src/uu/tail/src/parse.rs +++ b/src/uu/tail/src/parse.rs @@ -3,16 +3,34 @@ // * For the full copyright and license information, please view the LICENSE // * file that was distributed with this source code. -use std::ffi::OsString; +#[derive(PartialEq, Eq, Debug, Copy, Clone)] +pub struct ObsoleteArgs { + pub num: u64, + pub plus: bool, + pub lines: bool, + pub follow: bool, +} + +impl Default for ObsoleteArgs { + fn default() -> Self { + Self { + num: 10, + plus: false, + lines: true, + follow: false, + } + } +} #[derive(PartialEq, Eq, Debug)] pub enum ParseError { + OutOfRange, Overflow, Context, } /// Parses obsolete syntax /// tail -\[NUM\]\[bl\]\[f\] and tail +\[NUM\]\[bcl\]\[f\] // spell-checker:disable-line -pub fn parse_obsolete(src: &str) -> Option, ParseError>> { +pub fn parse_obsolete(src: &str) -> Option> { let mut chars = src.chars(); let sign = chars.next()?; if sign != '+' && sign != '-' { @@ -21,27 +39,27 @@ pub fn parse_obsolete(src: &str) -> Option let numbers: String = chars.clone().take_while(|&c| c.is_ascii_digit()).collect(); let has_num = !numbers.is_empty(); - let num: usize = if has_num { + let num: u64 = if has_num { if let Ok(num) = numbers.parse() { num } else { - return Some(Err(ParseError::Overflow)); + return Some(Err(ParseError::OutOfRange)); } } else { 10 }; let mut follow = false; - let mut mode = None; + let mut mode = 'l'; let mut first_char = true; for char in chars.skip_while(|&c| c.is_ascii_digit()) { - if sign == '-' && char == 'c' && !has_num { - // special case: -c should be handled by clap (is ambiguous) + if !has_num && first_char && sign == '-' && (char == 'c' || char == 'f') { + // special cases: -c, -f should be handled by clap (are ambiguous) return None; } else if char == 'f' { follow = true; } else if first_char && (char == 'b' || char == 'c' || char == 'l') { - mode = Some(char); + mode = char; } else if has_num && sign == '-' { return Some(Err(ParseError::Context)); } else { @@ -49,82 +67,81 @@ pub fn parse_obsolete(src: &str) -> Option } first_char = false; } + let multiplier = if mode == 'b' { 512 } else { 1 }; + let num = match num.checked_mul(multiplier) { + Some(n) => n, + None => return Some(Err(ParseError::Overflow)), + }; - let mut options = Vec::new(); - if follow { - options.push(OsString::from("-f")); - } - let mode = mode.unwrap_or('l'); - if mode == 'b' || mode == 'c' { - options.push(OsString::from("-c")); - let n = if mode == 'b' { 512 } else { 1 }; - let num = match num.checked_mul(n) { - Some(n) => n, - None => return Some(Err(ParseError::Overflow)), - }; - options.push(OsString::from(format!("{sign}{num}"))); - } else { - options.push(OsString::from("-n")); - options.push(OsString::from(format!("{sign}{num}"))); - } - Some(Ok(options.into_iter())) + Some(Ok(ObsoleteArgs { + num, + plus: sign == '+', + lines: mode == 'l', + follow, + })) } #[cfg(test)] mod tests { use super::*; - fn obsolete(src: &str) -> Option, ParseError>> { - let r = parse_obsolete(src); - match r { - Some(s) => match s { - Ok(v) => Some(Ok(v.map(|s| s.to_str().unwrap().to_owned()).collect())), - Err(e) => Some(Err(e)), - }, - None => None, - } - } - fn obsolete_result(src: &[&str]) -> Option, ParseError>> { - Some(Ok(src.iter().map(|s| s.to_string()).collect())) - } #[test] fn test_parse_numbers_obsolete() { - assert_eq!(obsolete("+2c"), obsolete_result(&["-c", "+2"])); - assert_eq!(obsolete("-5"), obsolete_result(&["-n", "-5"])); - assert_eq!(obsolete("-100"), obsolete_result(&["-n", "-100"])); - assert_eq!(obsolete("-2b"), obsolete_result(&["-c", "-1024"])); + assert_eq!( + parse_obsolete("+2c"), + Some(Ok(ObsoleteArgs { + num: 2, + plus: true, + lines: false, + follow: false, + })) + ); + assert_eq!( + parse_obsolete("-5"), + Some(Ok(ObsoleteArgs { + num: 5, + plus: false, + lines: true, + follow: false, + })) + ); + assert_eq!( + parse_obsolete("+100f"), + Some(Ok(ObsoleteArgs { + num: 100, + plus: true, + lines: true, + follow: true, + })) + ); + assert_eq!( + parse_obsolete("-2b"), + Some(Ok(ObsoleteArgs { + num: 1024, + plus: false, + lines: false, + follow: false, + })) + ); } #[test] fn test_parse_errors_obsolete() { - assert_eq!(obsolete("-5n"), Some(Err(ParseError::Context))); - assert_eq!(obsolete("-5c5"), Some(Err(ParseError::Context))); - assert_eq!(obsolete("-1vzc"), Some(Err(ParseError::Context))); - assert_eq!(obsolete("-5m"), Some(Err(ParseError::Context))); - assert_eq!(obsolete("-1k"), Some(Err(ParseError::Context))); - assert_eq!(obsolete("-1mmk"), Some(Err(ParseError::Context))); - assert_eq!(obsolete("-105kzm"), Some(Err(ParseError::Context))); - assert_eq!(obsolete("-1vz"), Some(Err(ParseError::Context))); + assert_eq!(parse_obsolete("-5n"), Some(Err(ParseError::Context))); + assert_eq!(parse_obsolete("-5c5"), Some(Err(ParseError::Context))); + assert_eq!(parse_obsolete("-1vzc"), Some(Err(ParseError::Context))); + assert_eq!(parse_obsolete("-5m"), Some(Err(ParseError::Context))); + assert_eq!(parse_obsolete("-1k"), Some(Err(ParseError::Context))); + assert_eq!(parse_obsolete("-1mmk"), Some(Err(ParseError::Context))); + assert_eq!(parse_obsolete("-105kzm"), Some(Err(ParseError::Context))); + assert_eq!(parse_obsolete("-1vz"), Some(Err(ParseError::Context))); assert_eq!( - obsolete("-1vzqvq"), // spell-checker:disable-line + parse_obsolete("-1vzqvq"), // spell-checker:disable-line Some(Err(ParseError::Context)) ); } #[test] fn test_parse_obsolete_no_match() { - assert_eq!(obsolete("-k"), None); - assert_eq!(obsolete("asd"), None); - assert_eq!(obsolete("-cc"), None); - } - #[test] - #[cfg(target_pointer_width = "64")] - fn test_parse_obsolete_overflow_x64() { - assert_eq!( - obsolete("-10000000000000000000000"), - Some(Err(ParseError::Overflow)) - ); - } - #[test] - #[cfg(target_pointer_width = "32")] - fn test_parse_obsolete_overflow_x32() { - assert_eq!(obsolete("-42949672960"), Some(Err(ParseError::Overflow))); + assert_eq!(parse_obsolete("-k"), None); + assert_eq!(parse_obsolete("asd"), None); + assert_eq!(parse_obsolete("-cc"), None); } } diff --git a/tests/by-util/test_tail.rs b/tests/by-util/test_tail.rs index 4644cbc029c..d1e5ba632c4 100644 --- a/tests/by-util/test_tail.rs +++ b/tests/by-util/test_tail.rs @@ -47,6 +47,13 @@ static FOLLOW_NAME_EXP: &str = "follow_name.expected"; #[cfg(not(windows))] const DEFAULT_SLEEP_INTERVAL_MILLIS: u64 = 1000; +// The binary integer "10000000" is *not* a valid UTF-8 encoding +// of a character: https://en.wikipedia.org/wiki/UTF-8#Encoding +#[cfg(unix)] +const INVALID_UTF8: u8 = 0x80; +#[cfg(windows)] +const INVALID_UTF16: u16 = 0xD800; + #[test] fn test_invalid_arg() { new_ucmd!().arg("--definitely-invalid").fails().code_is(1); @@ -469,16 +476,13 @@ fn test_follow_non_utf8_bytes() { // Now append some bytes that are not valid UTF-8. // - // The binary integer "10000000" is *not* a valid UTF-8 encoding - // of a character: https://en.wikipedia.org/wiki/UTF-8#Encoding - // // We also write the newline character because our implementation // of `tail` is attempting to read a line of input, so the // presence of a newline character will force the `follow()` // function to conclude reading input bytes and start writing them // to output. The newline character is not fundamental to this // test, it is just a requirement of the current implementation. - let expected = [0b10000000, b'\n']; + let expected = [INVALID_UTF8, b'\n']; at.append_bytes(FOOBAR_TXT, &expected); child @@ -4710,4 +4714,100 @@ fn test_gnu_args_err() { .no_stdout() .stderr_is("tail: option used in invalid context -- 5\n") .code_is(1); + scene + .ucmd() + .arg("-9999999999999999999b") + .fails() + .no_stdout() + .stderr_is("tail: invalid number: '-9999999999999999999b'\n") + .code_is(1); + scene + .ucmd() + .arg("-999999999999999999999b") + .fails() + .no_stdout() + .stderr_is( + "tail: invalid number: '-999999999999999999999b': Numerical result out of range\n", + ) + .code_is(1); +} + +#[test] +fn test_gnu_args_f() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let mut p = scene + .ucmd() + .set_stdin(Stdio::piped()) + .arg("+f") + .run_no_wait(); + p.make_assertion_with_delay(500).is_alive(); + p.kill() + .make_assertion() + .with_all_output() + .no_stderr() + .no_stdout(); + + let source = "file"; + at.touch(source); + let mut p = scene.ucmd().args(&["+f", source]).run_no_wait(); + p.make_assertion_with_delay(500).is_alive(); + p.kill() + .make_assertion() + .with_all_output() + .no_stderr() + .no_stdout(); +} + +#[test] +#[cfg(unix)] +fn test_obsolete_encoding_unix() { + use std::ffi::OsStr; + use std::os::unix::ffi::OsStrExt; + + let scene = TestScenario::new(util_name!()); + let invalid_utf8_arg = OsStr::from_bytes(&[b'-', INVALID_UTF8, b'b']); + let valid_utf8_arg = OsStr::from_bytes(&[b'-', b'b']); + + scene + .ucmd() + .arg(invalid_utf8_arg) + .fails() + .no_stdout() + .stderr_is("tail: bad argument encoding: '-�b'\n") + .code_is(1); + scene + .ucmd() + .args(&[valid_utf8_arg, invalid_utf8_arg]) + .fails() + .no_stdout() + .stderr_is("tail: bad argument encoding\n") + .code_is(1); +} + +#[test] +#[cfg(windows)] +fn test_obsolete_encoding_windows() { + use std::ffi::OsString; + use std::os::windows::ffi::OsStringExt; + + let scene = TestScenario::new(util_name!()); + let invalid_utf16_arg = OsString::from_wide(&['-' as u16, INVALID_UTF16, 'b' as u16]); + let valid_utf16_arg = OsString::from("-b"); + + scene + .ucmd() + .arg(&invalid_utf16_arg) + .fails() + .no_stdout() + .stderr_is("tail: bad argument encoding: '-�b'\n") + .code_is(1); + scene + .ucmd() + .args(&[&valid_utf16_arg, &invalid_utf16_arg]) + .fails() + .no_stdout() + .stderr_is("tail: bad argument encoding\n") + .code_is(1); } From f6edea2d0565d476d6725f57f20bb4b4abf16f20 Mon Sep 17 00:00:00 2001 From: Benjamin Bara Date: Sat, 18 Feb 2023 15:44:49 +0100 Subject: [PATCH 017/700] tail: enable non-utf8 paths --- src/uu/tail/src/args.rs | 12 ++++++++---- src/uu/tail/src/paths.rs | 13 +++++++------ tests/by-util/test_tail.rs | 16 ---------------- 3 files changed, 15 insertions(+), 26 deletions(-) diff --git a/src/uu/tail/src/args.rs b/src/uu/tail/src/args.rs index f18f99d1b58..7bf06f85374 100644 --- a/src/uu/tail/src/args.rs +++ b/src/uu/tail/src/args.rs @@ -13,6 +13,7 @@ use fundu::DurationParser; use is_terminal::IsTerminal; use same_file::Handle; use std::collections::VecDeque; +use std::ffi::OsString; use std::time::Duration; use uucore::error::{UResult, USimpleError, UUsageError}; use uucore::parse_size::{parse_size, ParseSizeError}; @@ -144,7 +145,7 @@ pub struct Settings { } impl Settings { - pub fn from_obsolete_args(args: &parse::ObsoleteArgs, name: Option<&str>) -> Self { + pub fn from_obsolete_args(args: &parse::ObsoleteArgs, name: Option) -> Self { let mut settings: Self = Self { sleep_sec: Duration::from_secs_f32(1.0), max_unchanged_stats: 5, @@ -159,7 +160,7 @@ impl Settings { } settings.mode = FilterMode::from_obsolete_args(args); let input = if let Some(name) = name { - Input::from(name.to_string()) + Input::from(&name) } else { Input::default() }; @@ -249,7 +250,7 @@ impl Settings { let mut inputs: VecDeque = matches .get_many::(options::ARG_FILES) - .map(|v| v.map(|string| Input::from(string.clone())).collect()) + .map(|v| v.map(|string| Input::from(&string)).collect()) .unwrap_or_default(); // apply default and add '-' to inputs if none is present @@ -575,7 +576,10 @@ mod tests { #[test] fn test_parse_obsolete_settings_f() { - let args = ObsoleteArgs { follow: true, ..Default::default() }; + let args = ObsoleteArgs { + follow: true, + ..Default::default() + }; let result = Settings::from_obsolete_args(&args, None); assert_eq!(result.follow, Some(FollowMode::Descriptor)); diff --git a/src/uu/tail/src/paths.rs b/src/uu/tail/src/paths.rs index d8e6ece9a22..b1cbc9bb8b4 100644 --- a/src/uu/tail/src/paths.rs +++ b/src/uu/tail/src/paths.rs @@ -6,6 +6,7 @@ // spell-checker:ignore tailable seekable stdlib (stdlib) use crate::text; +use std::ffi::OsStr; use std::fs::{File, Metadata}; use std::io::{Seek, SeekFrom}; #[cfg(unix)] @@ -26,21 +27,21 @@ pub struct Input { } impl Input { - // TODO: from &str may be the better choice - pub fn from(string: String) -> Self { - let kind = if string == text::DASH { + pub fn from>(string: &T) -> Self { + let valid_string = string.as_ref().to_str(); + let kind = if valid_string.is_some() && valid_string.unwrap() == text::DASH { InputKind::Stdin } else { - InputKind::File(PathBuf::from(&string)) + InputKind::File(PathBuf::from(string.as_ref())) }; let display_name = match kind { - InputKind::File(_) => string, + InputKind::File(_) => string.as_ref().to_string_lossy().to_string(), InputKind::Stdin => { if cfg!(unix) { text::STDIN_HEADER.to_string() } else { - string + string.as_ref().to_string_lossy().to_string() } } }; diff --git a/tests/by-util/test_tail.rs b/tests/by-util/test_tail.rs index d1e5ba632c4..b450d303bc7 100644 --- a/tests/by-util/test_tail.rs +++ b/tests/by-util/test_tail.rs @@ -4768,7 +4768,6 @@ fn test_obsolete_encoding_unix() { let scene = TestScenario::new(util_name!()); let invalid_utf8_arg = OsStr::from_bytes(&[b'-', INVALID_UTF8, b'b']); - let valid_utf8_arg = OsStr::from_bytes(&[b'-', b'b']); scene .ucmd() @@ -4777,13 +4776,6 @@ fn test_obsolete_encoding_unix() { .no_stdout() .stderr_is("tail: bad argument encoding: '-�b'\n") .code_is(1); - scene - .ucmd() - .args(&[valid_utf8_arg, invalid_utf8_arg]) - .fails() - .no_stdout() - .stderr_is("tail: bad argument encoding\n") - .code_is(1); } #[test] @@ -4794,7 +4786,6 @@ fn test_obsolete_encoding_windows() { let scene = TestScenario::new(util_name!()); let invalid_utf16_arg = OsString::from_wide(&['-' as u16, INVALID_UTF16, 'b' as u16]); - let valid_utf16_arg = OsString::from("-b"); scene .ucmd() @@ -4803,11 +4794,4 @@ fn test_obsolete_encoding_windows() { .no_stdout() .stderr_is("tail: bad argument encoding: '-�b'\n") .code_is(1); - scene - .ucmd() - .args(&[&valid_utf16_arg, &invalid_utf16_arg]) - .fails() - .no_stdout() - .stderr_is("tail: bad argument encoding\n") - .code_is(1); } From 9b49f368c7cf7fb91054f2b609432715efc3fbdc Mon Sep 17 00:00:00 2001 From: Benjamin Bara Date: Sun, 19 Feb 2023 14:24:07 +0100 Subject: [PATCH 018/700] tail: parse default before obsolete --- src/uu/tail/src/args.rs | 119 +++++++++++++++++++++++-------------- tests/by-util/test_tail.rs | 16 ++--- 2 files changed, 84 insertions(+), 51 deletions(-) diff --git a/src/uu/tail/src/args.rs b/src/uu/tail/src/args.rs index 7bf06f85374..9d0d6db7cb6 100644 --- a/src/uu/tail/src/args.rs +++ b/src/uu/tail/src/args.rs @@ -130,7 +130,7 @@ pub enum VerificationResult { NoOutput, } -#[derive(Debug, Default)] +#[derive(Debug)] pub struct Settings { pub follow: Option, pub max_unchanged_stats: u32, @@ -144,13 +144,26 @@ pub struct Settings { pub inputs: VecDeque, } -impl Settings { - pub fn from_obsolete_args(args: &parse::ObsoleteArgs, name: Option) -> Self { - let mut settings: Self = Self { - sleep_sec: Duration::from_secs_f32(1.0), +impl Default for Settings { + fn default() -> Self { + Self { max_unchanged_stats: 5, - ..Default::default() - }; + sleep_sec: Duration::from_secs_f32(1.0), + follow: Default::default(), + mode: Default::default(), + pid: Default::default(), + retry: Default::default(), + use_polling: Default::default(), + verbose: Default::default(), + presume_input_pipe: Default::default(), + inputs: Default::default(), + } + } +} + +impl Settings { + pub fn from_obsolete_args(args: &parse::ObsoleteArgs, name: Option<&OsString>) -> Self { + let mut settings: Self = Default::default(); if args.follow { settings.follow = if name.is_some() { Some(FollowMode::Name) @@ -170,25 +183,25 @@ impl Settings { pub fn from(matches: &clap::ArgMatches) -> UResult { let mut settings: Self = Self { - sleep_sec: Duration::from_secs_f32(1.0), - max_unchanged_stats: 5, + follow: if matches.get_flag(options::FOLLOW_RETRY) { + Some(FollowMode::Name) + } else if matches.value_source(options::FOLLOW) != Some(ValueSource::CommandLine) { + None + } else if matches.get_one::(options::FOLLOW) + == Some(String::from("name")).as_ref() + { + Some(FollowMode::Name) + } else { + Some(FollowMode::Descriptor) + }, + retry: matches.get_flag(options::RETRY) || matches.get_flag(options::FOLLOW_RETRY), + use_polling: matches.get_flag(options::USE_POLLING), + mode: FilterMode::from(matches)?, + verbose: matches.get_flag(options::verbosity::VERBOSE), + presume_input_pipe: matches.get_flag(options::PRESUME_INPUT_PIPE), ..Default::default() }; - settings.follow = if matches.get_flag(options::FOLLOW_RETRY) { - Some(FollowMode::Name) - } else if matches.value_source(options::FOLLOW) != Some(ValueSource::CommandLine) { - None - } else if matches.get_one::(options::FOLLOW) == Some(String::from("name")).as_ref() - { - Some(FollowMode::Name) - } else { - Some(FollowMode::Descriptor) - }; - - settings.retry = - matches.get_flag(options::RETRY) || matches.get_flag(options::FOLLOW_RETRY); - if let Some(source) = matches.get_one::(options::SLEEP_INT) { // Advantage of `fundu` over `Duration::(try_)from_secs_f64(source.parse().unwrap())`: // * doesn't panic on errors like `Duration::from_secs_f64` would. @@ -205,8 +218,6 @@ impl Settings { })?; } - settings.use_polling = matches.get_flag(options::USE_POLLING); - if let Some(s) = matches.get_one::(options::MAX_UNCHANGED_STATS) { settings.max_unchanged_stats = match s.parse::() { Ok(s) => s, @@ -246,8 +257,6 @@ impl Settings { } } - settings.mode = FilterMode::from(matches)?; - let mut inputs: VecDeque = matches .get_many::(options::ARG_FILES) .map(|v| v.map(|string| Input::from(&string)).collect()) @@ -258,13 +267,10 @@ impl Settings { inputs.push_front(Input::default()); } - settings.verbose = (matches.get_flag(options::verbosity::VERBOSE) || inputs.len() > 1) - && !matches.get_flag(options::verbosity::QUIET); + settings.verbose = inputs.len() > 1 && !matches.get_flag(options::verbosity::QUIET); settings.inputs = inputs; - settings.presume_input_pipe = matches.get_flag(options::PRESUME_INPUT_PIPE); - Ok(settings) } @@ -342,6 +348,19 @@ impl Settings { VerificationResult::Ok } + + pub fn is_default(&self) -> bool { + let default = Self::default(); + self.max_unchanged_stats == default.max_unchanged_stats + && self.sleep_sec == default.sleep_sec + && self.follow == default.follow + && self.mode == default.mode + && self.pid == default.pid + && self.retry == default.retry + && self.use_polling == default.use_polling + && (self.verbose == default.verbose || self.inputs.len() > 1) + && self.presume_input_pipe == default.presume_input_pipe + } } pub fn parse_obsolete(args: &str) -> UResult> { @@ -389,28 +408,42 @@ fn parse_num(src: &str) -> Result { }) } -pub fn parse_args(mut args: impl uucore::Args) -> UResult { - let first = args.next().unwrap(); - let second = match args.next() { +pub fn parse_args(args: impl uucore::Args) -> UResult { + let args_vec: Vec = args.collect(); + let clap_result = match uu_app().try_get_matches_from(args_vec.clone()) { + Ok(matches) => { + let settings = Settings::from(&matches)?; + if !settings.is_default() { + // non-default settings can't have obsolete arguments + return Ok(settings); + } + Ok(settings) + } + Err(err) => Err(err.into()), + }; + + // clap parsing failed or resulted to default -> check for obsolete/deprecated args + // argv[0] is always present + let second = match args_vec.get(1) { Some(second) => second, - None => return Settings::from(&uu_app().try_get_matches_from(vec![first])?), + None => return clap_result, }; let second_str = match second.to_str() { Some(second_str) => second_str, None => { - let second_string = second.to_string_lossy(); + let invalid_string = second.to_string_lossy(); return Err(USimpleError::new( 1, - format!("bad argument encoding: '{second_string}'"), + format!("bad argument encoding: '{invalid_string}'"), )); } }; match parse_obsolete(second_str)? { - Some(obsolete_args) => Ok(Settings::from_obsolete_args(&obsolete_args, args.next())), - None => { - let args = vec![first, second].into_iter().chain(args); - Settings::from(&uu_app().try_get_matches_from(args)?) - } + Some(obsolete_args) => Ok(Settings::from_obsolete_args( + &obsolete_args, + args_vec.get(2), + )), + None => clap_result, } } @@ -583,7 +616,7 @@ mod tests { let result = Settings::from_obsolete_args(&args, None); assert_eq!(result.follow, Some(FollowMode::Descriptor)); - let result = Settings::from_obsolete_args(&args, Some("test".into())); + let result = Settings::from_obsolete_args(&args, Some(&"file".into())); assert_eq!(result.follow, Some(FollowMode::Name)); } } diff --git a/tests/by-util/test_tail.rs b/tests/by-util/test_tail.rs index b450d303bc7..eb18e858f74 100644 --- a/tests/by-util/test_tail.rs +++ b/tests/by-util/test_tail.rs @@ -4737,11 +4737,9 @@ fn test_gnu_args_f() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; - let mut p = scene - .ucmd() - .set_stdin(Stdio::piped()) - .arg("+f") - .run_no_wait(); + let source = "file"; + at.touch(source); + let mut p = scene.ucmd().args(&["+f", source]).run_no_wait(); p.make_assertion_with_delay(500).is_alive(); p.kill() .make_assertion() @@ -4749,9 +4747,11 @@ fn test_gnu_args_f() { .no_stderr() .no_stdout(); - let source = "file"; - at.touch(source); - let mut p = scene.ucmd().args(&["+f", source]).run_no_wait(); + let mut p = scene + .ucmd() + .set_stdin(Stdio::piped()) + .arg("+f") + .run_no_wait(); p.make_assertion_with_delay(500).is_alive(); p.kill() .make_assertion() From 26a945f3b54d3e3d86dccda3dafd08f71092d198 Mon Sep 17 00:00:00 2001 From: Benjamin Bara Date: Sun, 19 Feb 2023 15:16:58 +0100 Subject: [PATCH 019/700] tail: simplify obsolete parsing --- src/uu/tail/src/args.rs | 117 ++++++++++++++++++------------------- src/uu/tail/src/parse.rs | 122 ++++++++++++++++++++++++++------------- src/uu/tail/src/paths.rs | 3 +- 3 files changed, 140 insertions(+), 102 deletions(-) diff --git a/src/uu/tail/src/args.rs b/src/uu/tail/src/args.rs index 9d0d6db7cb6..8e039b5f4b0 100644 --- a/src/uu/tail/src/args.rs +++ b/src/uu/tail/src/args.rs @@ -348,39 +348,33 @@ impl Settings { VerificationResult::Ok } - - pub fn is_default(&self) -> bool { - let default = Self::default(); - self.max_unchanged_stats == default.max_unchanged_stats - && self.sleep_sec == default.sleep_sec - && self.follow == default.follow - && self.mode == default.mode - && self.pid == default.pid - && self.retry == default.retry - && self.use_polling == default.use_polling - && (self.verbose == default.verbose || self.inputs.len() > 1) - && self.presume_input_pipe == default.presume_input_pipe - } } -pub fn parse_obsolete(args: &str) -> UResult> { - match parse::parse_obsolete(args) { - Some(Ok(args)) => Ok(Some(args)), +pub fn parse_obsolete(arg: &OsString, input: Option<&OsString>) -> UResult> { + match parse::parse_obsolete(arg) { + Some(Ok(args)) => Ok(Some(Settings::from_obsolete_args(&args, input))), None => Ok(None), - Some(Err(e)) => Err(USimpleError::new( - 1, - match e { - parse::ParseError::OutOfRange => format!( - "invalid number: {}: Numerical result out of range", - args.quote() - ), - parse::ParseError::Overflow => format!("invalid number: {}", args.quote()), - parse::ParseError::Context => format!( - "option used in invalid context -- {}", - args.chars().nth(1).unwrap_or_default() - ), - }, - )), + Some(Err(e)) => { + let arg_str = arg.to_string_lossy(); + Err(USimpleError::new( + 1, + match e { + parse::ParseError::OutOfRange => format!( + "invalid number: {}: Numerical result out of range", + arg_str.quote() + ), + parse::ParseError::Overflow => format!("invalid number: {}", arg_str.quote()), + // this ensures compatibility to GNU's error message (as tested in misc/tail) + parse::ParseError::Context => format!( + "option used in invalid context -- {}", + arg_str.chars().nth(1).unwrap_or_default() + ), + parse::ParseError::InvalidEncoding => { + format!("bad argument encoding: '{arg_str}'") + } + }, + )) + } } } @@ -410,39 +404,41 @@ fn parse_num(src: &str) -> Result { pub fn parse_args(args: impl uucore::Args) -> UResult { let args_vec: Vec = args.collect(); - let clap_result = match uu_app().try_get_matches_from(args_vec.clone()) { - Ok(matches) => { - let settings = Settings::from(&matches)?; - if !settings.is_default() { - // non-default settings can't have obsolete arguments - return Ok(settings); - } - Ok(settings) - } + let clap_args = uu_app().try_get_matches_from(args_vec.clone()); + let clap_result = match clap_args { + Ok(matches) => Ok(Settings::from(&matches)?), Err(err) => Err(err.into()), }; - // clap parsing failed or resulted to default -> check for obsolete/deprecated args - // argv[0] is always present - let second = match args_vec.get(1) { - Some(second) => second, - None => return clap_result, - }; - let second_str = match second.to_str() { - Some(second_str) => second_str, - None => { - let invalid_string = second.to_string_lossy(); - return Err(USimpleError::new( - 1, - format!("bad argument encoding: '{invalid_string}'"), - )); - } - }; - match parse_obsolete(second_str)? { - Some(obsolete_args) => Ok(Settings::from_obsolete_args( - &obsolete_args, - args_vec.get(2), - )), + // clap isn't able to handle obsolete syntax. + // therefore, we want to check further for obsolete arguments. + // argv[0] is always present, argv[1] might be obsolete arguments + // argv[2] might contain an input file, argv[3] isn't allowed in obsolete mode + if args_vec.len() != 2 && args_vec.len() != 3 { + return clap_result; + } + + // At this point, there are a few possible cases: + // + // 1. clap has succeeded and the arguments would be invalid for the obsolete syntax. + // 2. The case of `tail -c 5` is ambiguous. clap parses this as `tail -c5`, + // but it could also be interpreted as valid obsolete syntax (tail -c on file '5'). + // GNU chooses to interpret this as `tail -c5`, like clap. + // 3. `tail -f foo` is also ambiguous, but has the same effect in both cases. We can safely + // use the clap result here. + // 4. clap succeeded for obsolete arguments starting with '+', but misinterprets them as + // input files (e.g. 'tail +f'). + // 5. clap failed because of unknown flags, but possibly valid obsolete arguments + // (e.g. tail -l; tail -10c). + // + // In cases 4 & 5, we want to try parsing the obsolete arguments, which corresponds to + // checking whether clap succeeded or the first argument starts with '+'. + let possible_obsolete_args = &args_vec[1]; + if clap_result.is_ok() && !possible_obsolete_args.to_string_lossy().starts_with('+') { + return clap_result; + } + match parse_obsolete(possible_obsolete_args, args_vec.get(2))? { + Some(settings) => Ok(settings), None => clap_result, } } @@ -477,6 +473,7 @@ pub fn uu_app() -> Command { .num_args(0..=1) .require_equals(true) .value_parser(["descriptor", "name"]) + .overrides_with(options::FOLLOW) .help("Print the file as it grows"), ) .arg( diff --git a/src/uu/tail/src/parse.rs b/src/uu/tail/src/parse.rs index 2f4ebb62ebb..96cf1e9189a 100644 --- a/src/uu/tail/src/parse.rs +++ b/src/uu/tail/src/parse.rs @@ -3,6 +3,8 @@ // * For the full copyright and license information, please view the LICENSE // * file that was distributed with this source code. +use std::ffi::OsString; + #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub struct ObsoleteArgs { pub num: u64, @@ -27,20 +29,31 @@ pub enum ParseError { OutOfRange, Overflow, Context, + InvalidEncoding, } /// Parses obsolete syntax -/// tail -\[NUM\]\[bl\]\[f\] and tail +\[NUM\]\[bcl\]\[f\] // spell-checker:disable-line -pub fn parse_obsolete(src: &str) -> Option> { - let mut chars = src.chars(); - let sign = chars.next()?; - if sign != '+' && sign != '-' { +/// tail -\[NUM\]\[bcl\]\[f\] and tail +\[NUM\]\[bcl\]\[f\] // spell-checker:disable-line +pub fn parse_obsolete(src: &OsString) -> Option> { + let mut rest = match src.to_str() { + Some(src) => src, + None => return Some(Err(ParseError::InvalidEncoding)), + }; + let sign = if let Some(r) = rest.strip_prefix('-') { + rest = r; + '-' + } else if let Some(r) = rest.strip_prefix('+') { + rest = r; + '+' + } else { return None; - } + }; - let numbers: String = chars.clone().take_while(|&c| c.is_ascii_digit()).collect(); - let has_num = !numbers.is_empty(); + let end_num = rest + .find(|c: char| !c.is_ascii_digit()) + .unwrap_or(rest.len()); + let has_num = !rest[..end_num].is_empty(); let num: u64 = if has_num { - if let Ok(num) = numbers.parse() { + if let Ok(num) = rest[..end_num].parse() { num } else { return Some(Err(ParseError::OutOfRange)); @@ -48,25 +61,30 @@ pub fn parse_obsolete(src: &str) -> Option> { } else { 10 }; + rest = &rest[end_num..]; - let mut follow = false; - let mut mode = 'l'; - let mut first_char = true; - for char in chars.skip_while(|&c| c.is_ascii_digit()) { - if !has_num && first_char && sign == '-' && (char == 'c' || char == 'f') { - // special cases: -c, -f should be handled by clap (are ambiguous) - return None; - } else if char == 'f' { - follow = true; - } else if first_char && (char == 'b' || char == 'c' || char == 'l') { - mode = char; - } else if has_num && sign == '-' { + let mode = if let Some(r) = rest.strip_prefix('l') { + rest = r; + 'l' + } else if let Some(r) = rest.strip_prefix('c') { + rest = r; + 'c' + } else if let Some(r) = rest.strip_prefix('b') { + rest = r; + 'b' + } else { + 'l' + }; + + let follow = rest.contains('f'); + if !rest.chars().all(|f| f == 'f') { + // GNU allows an arbitrary amount of following fs, but nothing else + if sign == '-' && has_num { return Some(Err(ParseError::Context)); - } else { - return None; } - first_char = false; + return None; } + let multiplier = if mode == 'b' { 512 } else { 1 }; let num = match num.checked_mul(multiplier) { Some(n) => n, @@ -87,7 +105,7 @@ mod tests { #[test] fn test_parse_numbers_obsolete() { assert_eq!( - parse_obsolete("+2c"), + parse_obsolete(&OsString::from("+2c")), Some(Ok(ObsoleteArgs { num: 2, plus: true, @@ -96,7 +114,7 @@ mod tests { })) ); assert_eq!( - parse_obsolete("-5"), + parse_obsolete(&OsString::from("-5")), Some(Ok(ObsoleteArgs { num: 5, plus: false, @@ -105,7 +123,7 @@ mod tests { })) ); assert_eq!( - parse_obsolete("+100f"), + parse_obsolete(&OsString::from("+100f")), Some(Ok(ObsoleteArgs { num: 100, plus: true, @@ -114,7 +132,7 @@ mod tests { })) ); assert_eq!( - parse_obsolete("-2b"), + parse_obsolete(&OsString::from("-2b")), Some(Ok(ObsoleteArgs { num: 1024, plus: false, @@ -125,23 +143,47 @@ mod tests { } #[test] fn test_parse_errors_obsolete() { - assert_eq!(parse_obsolete("-5n"), Some(Err(ParseError::Context))); - assert_eq!(parse_obsolete("-5c5"), Some(Err(ParseError::Context))); - assert_eq!(parse_obsolete("-1vzc"), Some(Err(ParseError::Context))); - assert_eq!(parse_obsolete("-5m"), Some(Err(ParseError::Context))); - assert_eq!(parse_obsolete("-1k"), Some(Err(ParseError::Context))); - assert_eq!(parse_obsolete("-1mmk"), Some(Err(ParseError::Context))); - assert_eq!(parse_obsolete("-105kzm"), Some(Err(ParseError::Context))); - assert_eq!(parse_obsolete("-1vz"), Some(Err(ParseError::Context))); assert_eq!( - parse_obsolete("-1vzqvq"), // spell-checker:disable-line + parse_obsolete(&OsString::from("-5n")), + Some(Err(ParseError::Context)) + ); + assert_eq!( + parse_obsolete(&OsString::from("-5c5")), + Some(Err(ParseError::Context)) + ); + assert_eq!( + parse_obsolete(&OsString::from("-1vzc")), + Some(Err(ParseError::Context)) + ); + assert_eq!( + parse_obsolete(&OsString::from("-5m")), + Some(Err(ParseError::Context)) + ); + assert_eq!( + parse_obsolete(&OsString::from("-1k")), + Some(Err(ParseError::Context)) + ); + assert_eq!( + parse_obsolete(&OsString::from("-1mmk")), + Some(Err(ParseError::Context)) + ); + assert_eq!( + parse_obsolete(&OsString::from("-105kzm")), + Some(Err(ParseError::Context)) + ); + assert_eq!( + parse_obsolete(&OsString::from("-1vz")), + Some(Err(ParseError::Context)) + ); + assert_eq!( + parse_obsolete(&OsString::from("-1vzqvq")), // spell-checker:disable-line Some(Err(ParseError::Context)) ); } #[test] fn test_parse_obsolete_no_match() { - assert_eq!(parse_obsolete("-k"), None); - assert_eq!(parse_obsolete("asd"), None); - assert_eq!(parse_obsolete("-cc"), None); + assert_eq!(parse_obsolete(&OsString::from("-k")), None); + assert_eq!(parse_obsolete(&OsString::from("asd")), None); + assert_eq!(parse_obsolete(&OsString::from("-cc")), None); } } diff --git a/src/uu/tail/src/paths.rs b/src/uu/tail/src/paths.rs index b1cbc9bb8b4..4badd68660a 100644 --- a/src/uu/tail/src/paths.rs +++ b/src/uu/tail/src/paths.rs @@ -28,8 +28,7 @@ pub struct Input { impl Input { pub fn from>(string: &T) -> Self { - let valid_string = string.as_ref().to_str(); - let kind = if valid_string.is_some() && valid_string.unwrap() == text::DASH { + let kind = if string.as_ref() == Path::new(text::DASH) { InputKind::Stdin } else { InputKind::File(PathBuf::from(string.as_ref())) From f08f4d60165ef092f5031d2f46e4007f60287b5e Mon Sep 17 00:00:00 2001 From: Koki Ueha Date: Wed, 1 Mar 2023 12:44:20 +0000 Subject: [PATCH 020/700] chown: move help strings to markdown file --- src/uu/chown/chown.md | 8 ++++++++ src/uu/chown/src/chown.rs | 8 +++----- 2 files changed, 11 insertions(+), 5 deletions(-) create mode 100644 src/uu/chown/chown.md diff --git a/src/uu/chown/chown.md b/src/uu/chown/chown.md new file mode 100644 index 00000000000..11b653ea91d --- /dev/null +++ b/src/uu/chown/chown.md @@ -0,0 +1,8 @@ +# chown + +``` +chown [OPTION]... [OWNER][:[GROUP]] FILE... +chown [OPTION]... --reference=RFILE FILE... +``` + +Change file owner and group diff --git a/src/uu/chown/src/chown.rs b/src/uu/chown/src/chown.rs index 401703cdf34..3faecd571e4 100644 --- a/src/uu/chown/src/chown.rs +++ b/src/uu/chown/src/chown.rs @@ -9,8 +9,8 @@ use uucore::display::Quotable; pub use uucore::entries::{self, Group, Locate, Passwd}; -use uucore::format_usage; use uucore::perms::{chown_base, options, IfFrom}; +use uucore::{format_usage, help_about, help_usage}; use uucore::error::{FromIo, UResult, USimpleError}; @@ -19,11 +19,9 @@ use clap::{crate_version, Arg, ArgAction, ArgMatches, Command}; use std::fs; use std::os::unix::fs::MetadataExt; -static ABOUT: &str = "Change file owner and group"; +static ABOUT: &str = help_about!("chown.md"); -const USAGE: &str = "\ - {} [OPTION]... [OWNER][:[GROUP]] FILE... - {} [OPTION]... --reference=RFILE FILE..."; +const USAGE: &str = help_usage!("chown.md"); fn parse_gid_uid_and_filter(matches: &ArgMatches) -> UResult<(Option, Option, IfFrom)> { let filter = if let Some(spec) = matches.get_one::(options::FROM) { From 0d782e09c146c32066d08cc23b04cb51eb43cc45 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Tue, 28 Feb 2023 10:57:14 +0100 Subject: [PATCH 021/700] comm: implement --zero-terminated --- src/uu/comm/src/comm.rs | 108 ++++++++++++++++------ tests/by-util/test_comm.rs | 22 +++-- tests/fixtures/comm/a_nul | Bin 0 -> 3 bytes tests/fixtures/comm/ab_nul.expected | Bin 0 -> 9 bytes tests/fixtures/comm/ab_nul_total.expected | Bin 0 -> 21 bytes tests/fixtures/comm/b_nul | Bin 0 -> 3 bytes 6 files changed, 93 insertions(+), 37 deletions(-) create mode 100644 tests/fixtures/comm/a_nul create mode 100644 tests/fixtures/comm/ab_nul.expected create mode 100644 tests/fixtures/comm/ab_nul_total.expected create mode 100644 tests/fixtures/comm/b_nul diff --git a/src/uu/comm/src/comm.rs b/src/uu/comm/src/comm.rs index 3bc45b6a56e..02a9221d408 100644 --- a/src/uu/comm/src/comm.rs +++ b/src/uu/comm/src/comm.rs @@ -8,11 +8,11 @@ // spell-checker:ignore (ToDO) delim mkdelim use std::cmp::Ordering; +use std::fmt::Display; use std::fs::File; use std::io::{self, stdin, BufRead, BufReader, Stdin}; use std::path::Path; -use uucore::error::FromIo; -use uucore::error::UResult; +use uucore::error::{FromIo, UResult}; use uucore::{format_usage, help_about, help_usage}; use clap::{crate_version, Arg, ArgAction, ArgMatches, Command}; @@ -29,6 +29,7 @@ mod options { pub const FILE_1: &str = "FILE1"; pub const FILE_2: &str = "FILE2"; pub const TOTAL: &str = "total"; + pub const ZERO_TERMINATED: &str = "zero-terminated"; } fn column_width(col: &str, opts: &ArgMatches) -> usize { @@ -39,23 +40,66 @@ fn column_width(col: &str, opts: &ArgMatches) -> usize { } } -fn ensure_nl(line: &mut String) { - if !line.ends_with('\n') { - line.push('\n'); +#[repr(u8)] +#[derive(Clone, Copy)] +enum LineEnding { + Newline = b'\n', + Nul = 0, +} + +impl From for u8 { + fn from(line_ending: LineEnding) -> Self { + line_ending as Self + } +} + +impl From for LineEnding { + fn from(is_zero_terminated: bool) -> Self { + if is_zero_terminated { + Self::Nul + } else { + Self::Newline + } } } -enum LineReader { +impl Display for LineEnding { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Newline => writeln!(f), + Self::Nul => write!(f, "\0"), + } + } +} + +enum Input { Stdin(Stdin), FileIn(BufReader), } +struct LineReader { + line_ending: LineEnding, + input: Input, +} + impl LineReader { - fn read_line(&mut self, buf: &mut String) -> io::Result { - match *self { - Self::Stdin(ref mut r) => r.read_line(buf), - Self::FileIn(ref mut r) => r.read_line(buf), + fn new(input: Input, line_ending: LineEnding) -> Self { + Self { input, line_ending } + } + + fn read_line(&mut self, buf: &mut Vec) -> io::Result { + let line_ending = self.line_ending.into(); + + let result = match &mut self.input { + Input::Stdin(r) => r.lock().read_until(line_ending, buf), + Input::FileIn(r) => r.read_until(line_ending, buf), + }; + + if !buf.ends_with(&[line_ending]) { + buf.push(line_ending); } + + result } } @@ -71,9 +115,9 @@ fn comm(a: &mut LineReader, b: &mut LineReader, opts: &ArgMatches) { let delim_col_2 = delim.repeat(width_col_1); let delim_col_3 = delim.repeat(width_col_1 + width_col_2); - let ra = &mut String::new(); + let ra = &mut Vec::new(); let mut na = a.read_line(ra); - let rb = &mut String::new(); + let rb = &mut Vec::new(); let mut nb = b.read_line(rb); let mut total_col_1 = 0; @@ -96,8 +140,7 @@ fn comm(a: &mut LineReader, b: &mut LineReader, opts: &ArgMatches) { match ord { Ordering::Less => { if !opts.get_flag(options::COLUMN_1) { - ensure_nl(ra); - print!("{ra}"); + print!("{}", String::from_utf8_lossy(ra)); } ra.clear(); na = a.read_line(ra); @@ -105,8 +148,7 @@ fn comm(a: &mut LineReader, b: &mut LineReader, opts: &ArgMatches) { } Ordering::Greater => { if !opts.get_flag(options::COLUMN_2) { - ensure_nl(rb); - print!("{delim_col_2}{rb}"); + print!("{delim_col_2}{}", String::from_utf8_lossy(rb)); } rb.clear(); nb = b.read_line(rb); @@ -114,8 +156,7 @@ fn comm(a: &mut LineReader, b: &mut LineReader, opts: &ArgMatches) { } Ordering::Equal => { if !opts.get_flag(options::COLUMN_3) { - ensure_nl(ra); - print!("{delim_col_3}{ra}"); + print!("{delim_col_3}{}", String::from_utf8_lossy(ra)); } ra.clear(); rb.clear(); @@ -127,17 +168,20 @@ fn comm(a: &mut LineReader, b: &mut LineReader, opts: &ArgMatches) { } if opts.get_flag(options::TOTAL) { - println!("{total_col_1}{delim}{total_col_2}{delim}{total_col_3}{delim}total"); + let line_ending = LineEnding::from(opts.get_flag(options::ZERO_TERMINATED)); + print!("{total_col_1}{delim}{total_col_2}{delim}{total_col_3}{delim}total{line_ending}"); } } -fn open_file(name: &str) -> io::Result { - match name { - "-" => Ok(LineReader::Stdin(stdin())), - _ => { - let f = File::open(Path::new(name))?; - Ok(LineReader::FileIn(BufReader::new(f))) - } +fn open_file(name: &str, line_ending: LineEnding) -> io::Result { + if name == "-" { + Ok(LineReader::new(Input::Stdin(stdin()), line_ending)) + } else { + let f = File::open(Path::new(name))?; + Ok(LineReader::new( + Input::FileIn(BufReader::new(f)), + line_ending, + )) } } @@ -146,10 +190,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args.collect_lossy(); let matches = uu_app().try_get_matches_from(args)?; + let line_ending = LineEnding::from(matches.get_flag(options::ZERO_TERMINATED)); let filename1 = matches.get_one::(options::FILE_1).unwrap(); let filename2 = matches.get_one::(options::FILE_2).unwrap(); - let mut f1 = open_file(filename1).map_err_context(|| filename1.to_string())?; - let mut f2 = open_file(filename2).map_err_context(|| filename2.to_string())?; + let mut f1 = open_file(filename1, line_ending).map_err_context(|| filename1.to_string())?; + let mut f2 = open_file(filename2, line_ending).map_err_context(|| filename2.to_string())?; comm(&mut f1, &mut f2, &matches); Ok(()) @@ -187,6 +232,13 @@ pub fn uu_app() -> Command { .default_value(options::DELIMITER_DEFAULT) .hide_default_value(true), ) + .arg( + Arg::new(options::ZERO_TERMINATED) + .long(options::ZERO_TERMINATED) + .short('z') + .help("line delimiter is NUL, not newline") + .action(ArgAction::SetTrue), + ) .arg( Arg::new(options::FILE_1) .required(true) diff --git a/tests/by-util/test_comm.rs b/tests/by-util/test_comm.rs index 05374690819..aa0791f97ae 100644 --- a/tests/by-util/test_comm.rs +++ b/tests/by-util/test_comm.rs @@ -95,19 +95,23 @@ fn output_delimiter_nul() { .stdout_only_fixture("ab_delimiter_nul.expected"); } -// even though (info) documentation suggests this is an option -// in latest GNU Coreutils comm, it actually is not. -// this test is essentially an alarm in case some well-intending -// developer implements it. -//marked as unimplemented as error message not set yet. -#[cfg_attr(not(feature = "test_unimplemented"), ignore)] #[test] fn zero_terminated() { for param in ["-z", "--zero-terminated"] { new_ucmd!() - .args(&[param, "a", "b"]) - .fails() - .stderr_only("error to be defined"); + .args(&[param, "a_nul", "b_nul"]) + .succeeds() + .stdout_only_fixture("ab_nul.expected"); + } +} + +#[test] +fn zero_terminated_with_total() { + for param in ["-z", "--zero-terminated"] { + new_ucmd!() + .args(&[param, "--total", "a_nul", "b_nul"]) + .succeeds() + .stdout_only_fixture("ab_nul_total.expected"); } } diff --git a/tests/fixtures/comm/a_nul b/tests/fixtures/comm/a_nul new file mode 100644 index 0000000000000000000000000000000000000000..3142c801ccd54e1a9bc9d587e5ab16369ead768d GIT binary patch literal 3 KcmYdfr~&{1pa9$e literal 0 HcmV?d00001 diff --git a/tests/fixtures/comm/ab_nul.expected b/tests/fixtures/comm/ab_nul.expected new file mode 100644 index 0000000000000000000000000000000000000000..f826106bb1926f3ce2c6553b72bb0a007c432f4f GIT binary patch literal 9 QcmYdf;7nrR Date: Wed, 1 Mar 2023 15:45:41 -0300 Subject: [PATCH 022/700] mktemp: fix test_prefix_template_separator adding -p param --- tests/by-util/test_mktemp.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/by-util/test_mktemp.rs b/tests/by-util/test_mktemp.rs index 7c3d621506b..b6e3b2076c9 100644 --- a/tests/by-util/test_mktemp.rs +++ b/tests/by-util/test_mktemp.rs @@ -21,9 +21,9 @@ static TEST_TEMPLATE6: &str = "tempXXXlate"; // spell-checker:disable-line static TEST_TEMPLATE7: &str = "XXXtemplate"; // spell-checker:disable-line #[cfg(unix)] static TEST_TEMPLATE8: &str = "tempXXXl/ate"; -static TEST_TEMPLATE9: &str = "a.XXXX"; #[cfg(windows)] static TEST_TEMPLATE8: &str = "tempXXXl\\ate"; +static TEST_TEMPLATE9: &str = "a.XXXX"; #[cfg(not(windows))] const TMPDIR: &str = "TMPDIR"; @@ -575,7 +575,7 @@ fn test_template_path_separator() { /// Test that a prefix with a point is valid. #[test] fn test_prefix_template_separator() { - new_ucmd!().args(&["-t", TEST_TEMPLATE9]).succeeds(); + new_ucmd!().args(&["-p", ".", "-t", TEST_TEMPLATE9]).succeeds(); } #[test] From 18870e37874ac9401e2244ff990f657fb22943b4 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 2 Mar 2023 14:23:46 +0100 Subject: [PATCH 023/700] GH action: be consistent in the install --- .github/workflows/GnuTests.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/GnuTests.yml b/.github/workflows/GnuTests.yml index fd2d668351d..b74364f1461 100644 --- a/.github/workflows/GnuTests.yml +++ b/.github/workflows/GnuTests.yml @@ -86,7 +86,7 @@ jobs: run: | ## Install dependencies sudo apt-get update - sudo apt-get install autoconf autopoint bison texinfo gperf gcc g++ gdb python3-pyinotify jq valgrind libexpect-perl + sudo apt-get install -y autoconf autopoint bison texinfo gperf gcc g++ gdb python3-pyinotify jq valgrind libexpect-perl - name: Add various locales shell: bash run: | @@ -316,8 +316,8 @@ jobs: - name: Install dependencies run: | ## Install dependencies - sudo apt update - sudo apt install autoconf autopoint bison texinfo gperf gcc g++ gdb python3-pyinotify jq valgrind libexpect-perl -y + sudo apt-get update + sudo apt-get install -y autoconf autopoint bison texinfo gperf gcc g++ gdb python3-pyinotify jq valgrind libexpect-perl - name: Add various locales run: | ## Add various locales From 794df2ae7a53dfec25e658d53515dc43cc9d93cd Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 2 Mar 2023 14:24:43 +0100 Subject: [PATCH 024/700] GH action: install missing dependencies Fails with: ``` 2023-03-01T22:02:40.2587471Z configure: WARNING: libacl development library was not found or not usable. 2023-03-01T22:02:40.2588131Z configure: WARNING: GNU coreutils will be built without ACL support. 2023-03-01T22:03:03.7621309Z configure: WARNING: libattr development library was not found or not usable. 2023-03-01T22:03:03.7621976Z configure: WARNING: GNU coreutils will be built without xattr support. 2023-03-01T22:03:04.6538269Z configure: WARNING: libcap library was not found or not usable. 2023-03-01T22:03:04.6539022Z configure: WARNING: GNU coreutils will be built without capability support. ``` --- .github/workflows/GnuTests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/GnuTests.yml b/.github/workflows/GnuTests.yml index b74364f1461..7c8c307ec2b 100644 --- a/.github/workflows/GnuTests.yml +++ b/.github/workflows/GnuTests.yml @@ -86,7 +86,7 @@ jobs: run: | ## Install dependencies sudo apt-get update - sudo apt-get install -y autoconf autopoint bison texinfo gperf gcc g++ gdb python3-pyinotify jq valgrind libexpect-perl + sudo apt-get install -y autoconf autopoint bison texinfo gperf gcc g++ gdb python3-pyinotify jq valgrind libexpect-perl libacl1-dev libattr1-dev libcap-dev - name: Add various locales shell: bash run: | @@ -317,7 +317,7 @@ jobs: run: | ## Install dependencies sudo apt-get update - sudo apt-get install -y autoconf autopoint bison texinfo gperf gcc g++ gdb python3-pyinotify jq valgrind libexpect-perl + sudo apt-get install -y autoconf autopoint bison texinfo gperf gcc g++ gdb python3-pyinotify jq valgrind libexpect-perl libacl1-dev libattr1-dev libcap-dev - name: Add various locales run: | ## Add various locales From a7a55c743349b842dc9403c04fd42fe567b56c2d Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Thu, 2 Mar 2023 16:18:42 +0100 Subject: [PATCH 025/700] comm: allow multiple occurrence of -z --- src/uu/comm/src/comm.rs | 1 + tests/by-util/test_comm.rs | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/src/uu/comm/src/comm.rs b/src/uu/comm/src/comm.rs index 02a9221d408..26e70403793 100644 --- a/src/uu/comm/src/comm.rs +++ b/src/uu/comm/src/comm.rs @@ -236,6 +236,7 @@ pub fn uu_app() -> Command { Arg::new(options::ZERO_TERMINATED) .long(options::ZERO_TERMINATED) .short('z') + .overrides_with(options::ZERO_TERMINATED) .help("line delimiter is NUL, not newline") .action(ArgAction::SetTrue), ) diff --git a/tests/by-util/test_comm.rs b/tests/by-util/test_comm.rs index aa0791f97ae..0ee5c44c28b 100644 --- a/tests/by-util/test_comm.rs +++ b/tests/by-util/test_comm.rs @@ -105,6 +105,16 @@ fn zero_terminated() { } } +#[test] +fn zero_terminated_provided_multiple_times() { + for param in ["-z", "--zero-terminated"] { + new_ucmd!() + .args(&[param, param, param, "a_nul", "b_nul"]) + .succeeds() + .stdout_only_fixture("ab_nul.expected"); + } +} + #[test] fn zero_terminated_with_total() { for param in ["-z", "--zero-terminated"] { From 2bae3dd4f2c49ad44972df83bd2740ef36415dc6 Mon Sep 17 00:00:00 2001 From: Alexander Kunde Date: Thu, 2 Mar 2023 16:29:43 +0100 Subject: [PATCH 026/700] wc: move help strings to markdown file --- src/uu/wc/src/wc.rs | 7 +++---- src/uu/wc/wc.md | 9 +++++++++ 2 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 src/uu/wc/wc.md diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index 4a4838350df..21c4643c6cd 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -15,7 +15,7 @@ use count_fast::{count_bytes_chars_and_lines_fast, count_bytes_fast}; use countable::WordCountable; use unicode_width::UnicodeWidthChar; use utf8::{BufReadDecoder, BufReadDecoderError}; -use uucore::{format_usage, show}; +use uucore::{format_usage, show, help_about, help_usage}; use word_count::{TitledWordCount, WordCount}; use clap::{crate_version, Arg, ArgAction, ArgMatches, Command}; @@ -98,9 +98,8 @@ impl Settings { } } -static ABOUT: &str = "Display newline, word, and byte counts for each FILE, and a total line if -more than one FILE is specified. With no FILE, or when FILE is -, read standard input."; -const USAGE: &str = "{} [OPTION]... [FILE]..."; +static ABOUT: &str = help_about!("wc.md"); +const USAGE: &str = help_usage!("wc.md"); pub mod options { pub static BYTES: &str = "bytes"; diff --git a/src/uu/wc/wc.md b/src/uu/wc/wc.md new file mode 100644 index 00000000000..f1b243606ca --- /dev/null +++ b/src/uu/wc/wc.md @@ -0,0 +1,9 @@ +# wc + +## Usage +``` +wc [OPTION]... [FILE]... +``` + +Display newline, word, and byte counts for each FILE, and a total line if +more than one FILE is specified. With no FILE, or when FILE is -, read standard input. From df5ceaaa97e4e291a31b1ab7645eaef17566777d Mon Sep 17 00:00:00 2001 From: Alexander Kunde Date: Thu, 2 Mar 2023 18:00:31 +0100 Subject: [PATCH 027/700] truncate: move help strings to markdown file --- src/uu/truncate/src/truncate.rs | 26 +++++--------------------- src/uu/truncate/truncate.md | 28 ++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 21 deletions(-) create mode 100644 src/uu/truncate/truncate.md diff --git a/src/uu/truncate/src/truncate.rs b/src/uu/truncate/src/truncate.rs index 09086531321..c71f5079bde 100644 --- a/src/uu/truncate/src/truncate.rs +++ b/src/uu/truncate/src/truncate.rs @@ -14,7 +14,7 @@ use std::os::unix::fs::FileTypeExt; use std::path::Path; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; -use uucore::format_usage; +use uucore::{format_usage, help_about, help_section, help_usage}; use uucore::parse_size::{parse_size, ParseSizeError}; #[derive(Debug, Eq, PartialEq)] @@ -73,25 +73,9 @@ impl TruncateMode { } } -const ABOUT: &str = "Shrink or extend the size of each file to the specified size."; -const USAGE: &str = "{} [OPTION]... [FILE]..."; -const LONG_USAGE: &str = "\ -SIZE is an integer with an optional prefix and optional unit. -The available units (K, M, G, T, P, E, Z, and Y) use the following format: - 'KB' => 1000 (kilobytes) - 'K' => 1024 (kibibytes) - 'MB' => 1000*1000 (megabytes) - 'M' => 1024*1024 (mebibytes) - 'GB' => 1000*1000*1000 (gigabytes) - 'G' => 1024*1024*1024 (gibibytes) -SIZE may also be prefixed by one of the following to adjust the size of each -file based on its current size: - '+' => extend by - '-' => reduce by - '<' => at most - '>' => at least - '/' => round down to multiple of - '%' => round up to multiple of"; +const ABOUT: &str = help_about!("truncate.md"); +const AFTER_HELP: &str = help_section!("after help", "truncate.md"); +const USAGE: &str = help_usage!("truncate.md"); pub mod options { pub static IO_BLOCKS: &str = "io-blocks"; @@ -104,7 +88,7 @@ pub mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app() - .after_help(LONG_USAGE) + .after_help(AFTER_HELP) .try_get_matches_from(args) .map_err(|e| { e.print().expect("Error writing clap::Error"); diff --git a/src/uu/truncate/truncate.md b/src/uu/truncate/truncate.md new file mode 100644 index 00000000000..64ab4e01922 --- /dev/null +++ b/src/uu/truncate/truncate.md @@ -0,0 +1,28 @@ +# truncate + +## Usage + +```sh +truncate [OPTION]... [FILE]... +``` + +Shrink or extend the size of each file to the specified size. + +## After help + +SIZE is an integer with an optional prefix and optional unit. +The available units (K, M, G, T, P, E, Z, and Y) use the following format: + 'KB' => 1000 (kilobytes) + 'K' => 1024 (kibibytes) + 'MB' => 1000*1000 (megabytes) + 'M' => 1024*1024 (mebibytes) + 'GB' => 1000*1000*1000 (gigabytes) + 'G' => 1024*1024*1024 (gibibytes) +SIZE may also be prefixed by one of the following to adjust the size of each +file based on its current size: + '+' => extend by + '-' => reduce by + '<' => at most + '>' => at least + '/' => round down to multiple of + '%' => round up to multiple of \ No newline at end of file From 427611122229b08cb3f8f74ac318a36dceb82f0f Mon Sep 17 00:00:00 2001 From: Alexander Kunde Date: Thu, 2 Mar 2023 18:10:53 +0100 Subject: [PATCH 028/700] change ABOUT from static to const --- src/uu/wc/src/wc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index 21c4643c6cd..c1f4a2db1ad 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -98,7 +98,7 @@ impl Settings { } } -static ABOUT: &str = help_about!("wc.md"); +const ABOUT: &str = help_about!("wc.md"); const USAGE: &str = help_usage!("wc.md"); pub mod options { From 4bae3e0cd977f60a2cc17cc5ca483db4ed7d17c6 Mon Sep 17 00:00:00 2001 From: Alexander Kunde Date: Thu, 2 Mar 2023 18:14:10 +0100 Subject: [PATCH 029/700] remove redundant line --- src/uu/wc/wc.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/uu/wc/wc.md b/src/uu/wc/wc.md index f1b243606ca..db470a423d6 100644 --- a/src/uu/wc/wc.md +++ b/src/uu/wc/wc.md @@ -1,7 +1,6 @@ # wc -## Usage -``` +```sh wc [OPTION]... [FILE]... ``` From b73d5206f502a46d13dc1d51e1907547d2a2a862 Mon Sep 17 00:00:00 2001 From: Alexander Kunde Date: Thu, 2 Mar 2023 18:18:40 +0100 Subject: [PATCH 030/700] remove redundant line in markdown --- src/uu/truncate/truncate.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/uu/truncate/truncate.md b/src/uu/truncate/truncate.md index 64ab4e01922..36e71e9995c 100644 --- a/src/uu/truncate/truncate.md +++ b/src/uu/truncate/truncate.md @@ -1,7 +1,5 @@ # truncate -## Usage - ```sh truncate [OPTION]... [FILE]... ``` From aead80efdbb5e87f2ba72817599a142212099ffb Mon Sep 17 00:00:00 2001 From: Alexander Kunde Date: Thu, 2 Mar 2023 18:55:11 +0100 Subject: [PATCH 031/700] cargo fmt --- src/uu/wc/src/wc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index c1f4a2db1ad..0b7b164a810 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -15,7 +15,7 @@ use count_fast::{count_bytes_chars_and_lines_fast, count_bytes_fast}; use countable::WordCountable; use unicode_width::UnicodeWidthChar; use utf8::{BufReadDecoder, BufReadDecoderError}; -use uucore::{format_usage, show, help_about, help_usage}; +use uucore::{format_usage, help_about, help_usage, show}; use word_count::{TitledWordCount, WordCount}; use clap::{crate_version, Arg, ArgAction, ArgMatches, Command}; From 73bb60d72490cefbf8926ca170a83720dd13bacd Mon Sep 17 00:00:00 2001 From: Alexander Kunde Date: Thu, 2 Mar 2023 18:56:08 +0100 Subject: [PATCH 032/700] cargo fmt --- src/uu/truncate/src/truncate.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/truncate/src/truncate.rs b/src/uu/truncate/src/truncate.rs index c71f5079bde..f050b52b4fc 100644 --- a/src/uu/truncate/src/truncate.rs +++ b/src/uu/truncate/src/truncate.rs @@ -14,8 +14,8 @@ use std::os::unix::fs::FileTypeExt; use std::path::Path; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; -use uucore::{format_usage, help_about, help_section, help_usage}; use uucore::parse_size::{parse_size, ParseSizeError}; +use uucore::{format_usage, help_about, help_section, help_usage}; #[derive(Debug, Eq, PartialEq)] enum TruncateMode { From b94a0d2ebea6e151ad52f32f96b8ea9f09bb5a4d Mon Sep 17 00:00:00 2001 From: ZauJulio Date: Thu, 2 Mar 2023 15:30:55 -0300 Subject: [PATCH 033/700] mktemp: fix test fmt --- tests/by-util/test_mktemp.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/by-util/test_mktemp.rs b/tests/by-util/test_mktemp.rs index b6e3b2076c9..8d615899cf2 100644 --- a/tests/by-util/test_mktemp.rs +++ b/tests/by-util/test_mktemp.rs @@ -575,7 +575,9 @@ fn test_template_path_separator() { /// Test that a prefix with a point is valid. #[test] fn test_prefix_template_separator() { - new_ucmd!().args(&["-p", ".", "-t", TEST_TEMPLATE9]).succeeds(); + new_ucmd!() + .args(&["-p", ".", "-t", TEST_TEMPLATE9]) + .succeeds(); } #[test] From d29119728af09da6cc9955f7832a3d55dd2bb758 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guilherme=20Gon=C3=A7alves?= Date: Tue, 28 Feb 2023 10:06:42 +0000 Subject: [PATCH 034/700] tests/du: run test_du_time with TZ=UTC. du --time formats a file's timestamp according to the local timezone, but the test implicitly assumed UTC. This caused it to fail when running locally in my UTC+1 machine. $ cargo test --features "du touch" --no-default-features Failure: https://gist.github.com/eggpi/651e01559b7c59e9457c1b22fe4c0c19 --- tests/by-util/test_du.rs | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index 1232beda97f..4a3cc353072 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -391,7 +391,12 @@ fn test_du_h_flag_empty_file() { fn test_du_time() { let ts = TestScenario::new(util_name!()); + // du --time formats the timestamp according to the local timezone. We set the TZ + // environment variable to UTC in the commands below to ensure consistent outputs + // and test results regardless of the timezone of the machine this test runs in. + ts.ccmd("touch") + .env("TZ", "UTC") .arg("-a") .arg("-t") .arg("201505150000") @@ -399,19 +404,35 @@ fn test_du_time() { .succeeds(); ts.ccmd("touch") + .env("TZ", "UTC") .arg("-m") .arg("-t") .arg("201606160000") .arg("date_test") .succeeds(); - let result = ts.ucmd().arg("--time").arg("date_test").succeeds(); + let result = ts + .ucmd() + .env("TZ", "UTC") + .arg("--time") + .arg("date_test") + .succeeds(); result.stdout_only("0\t2016-06-16 00:00\tdate_test\n"); - let result = ts.ucmd().arg("--time=atime").arg("date_test").succeeds(); + let result = ts + .ucmd() + .env("TZ", "UTC") + .arg("--time=atime") + .arg("date_test") + .succeeds(); result.stdout_only("0\t2015-05-15 00:00\tdate_test\n"); - let result = ts.ucmd().arg("--time=ctime").arg("date_test").succeeds(); + let result = ts + .ucmd() + .env("TZ", "UTC") + .arg("--time=ctime") + .arg("date_test") + .succeeds(); result.stdout_only("0\t2016-06-16 00:00\tdate_test\n"); if birth_supported() { From 9fb6477c895fc9bb590c8f088f46a89cd76b984e Mon Sep 17 00:00:00 2001 From: zleyyij <75810274+zleyyij@users.noreply.github.com> Date: Fri, 3 Mar 2023 04:37:09 -0700 Subject: [PATCH 035/700] chcon: move help strings to a markdown file (#4437) * arch: move help strings to a markdown file #4368 --------- Co-authored-by: zleyyij Co-authored-by: Sylvestre Ledru Co-authored-by: Terts Diepraam --- src/uu/chcon/chcon.md | 11 +++++++++++ src/uu/chcon/src/chcon.rs | 14 ++++---------- 2 files changed, 15 insertions(+), 10 deletions(-) create mode 100644 src/uu/chcon/chcon.md diff --git a/src/uu/chcon/chcon.md b/src/uu/chcon/chcon.md new file mode 100644 index 00000000000..c4266be6e9d --- /dev/null +++ b/src/uu/chcon/chcon.md @@ -0,0 +1,11 @@ + +# chcon + +``` +chcon [OPTION]... CONTEXT FILE... +chcon [OPTION]... [-u USER] [-r ROLE] [-l RANGE] [-t TYPE] FILE... +chcon [OPTION]... --reference=RFILE FILE... +``` + +Change the SELinux security context of each FILE to CONTEXT. +With --reference, change the security context of each FILE to that of RFILE. \ No newline at end of file diff --git a/src/uu/chcon/src/chcon.rs b/src/uu/chcon/src/chcon.rs index 3acd2ea430c..2f316393a22 100644 --- a/src/uu/chcon/src/chcon.rs +++ b/src/uu/chcon/src/chcon.rs @@ -1,11 +1,9 @@ // spell-checker:ignore (vars) RFILE - #![allow(clippy::upper_case_acronyms)] use clap::builder::ValueParser; use uucore::error::{UResult, USimpleError, UUsageError}; -use uucore::format_usage; -use uucore::{display::Quotable, show_error, show_warning}; +use uucore::{display::Quotable, format_usage, help_about, help_usage, show_error, show_warning}; use clap::{Arg, ArgAction, Command}; use selinux::{OpaqueSecurityContext, SecurityContext}; @@ -21,13 +19,9 @@ mod fts; use errors::*; -static VERSION: &str = env!("CARGO_PKG_VERSION"); -static ABOUT: &str = "Change the SELinux security context of each FILE to CONTEXT. \n\ - With --reference, change the security context of each FILE to that of RFILE."; -const USAGE: &str = "\ - {} [OPTION]... CONTEXT FILE... \n \ - {} [OPTION]... [-u USER] [-r ROLE] [-l RANGE] [-t TYPE] FILE... \n \ - {} [OPTION]... --reference=RFILE FILE..."; +const VERSION: &str = env!("CARGO_PKG_VERSION"); +const ABOUT: &str = help_about!("chcon.md"); +const USAGE: &str = help_usage!("chcon.md"); pub mod options { pub static HELP: &str = "help"; From c35d1762aaf758452fbfaa68337446c490c55766 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 3 Mar 2023 12:53:45 +0100 Subject: [PATCH 036/700] Add RFILE to the exclude list --- src/uu/chown/chown.md | 1 + 1 file changed, 1 insertion(+) diff --git a/src/uu/chown/chown.md b/src/uu/chown/chown.md index 11b653ea91d..83101c74c73 100644 --- a/src/uu/chown/chown.md +++ b/src/uu/chown/chown.md @@ -1,3 +1,4 @@ + # chown ``` From 53d3d4d616d81140923d9ce74cd99f1862cd16c7 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 3 Mar 2023 13:12:49 +0100 Subject: [PATCH 037/700] remove the sh in the syntax --- src/uu/truncate/truncate.md | 1 - 1 file changed, 1 deletion(-) diff --git a/src/uu/truncate/truncate.md b/src/uu/truncate/truncate.md index 36e71e9995c..841c4b16c97 100644 --- a/src/uu/truncate/truncate.md +++ b/src/uu/truncate/truncate.md @@ -1,6 +1,5 @@ # truncate -```sh truncate [OPTION]... [FILE]... ``` From 2085d4d4ab1ec1ca977129466c2872ff8fc8728f Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 3 Mar 2023 13:52:47 +0100 Subject: [PATCH 038/700] remove the sh in the syntax --- src/uu/wc/wc.md | 1 - 1 file changed, 1 deletion(-) diff --git a/src/uu/wc/wc.md b/src/uu/wc/wc.md index db470a423d6..3d0014b37e0 100644 --- a/src/uu/wc/wc.md +++ b/src/uu/wc/wc.md @@ -1,6 +1,5 @@ # wc -```sh wc [OPTION]... [FILE]... ``` From 422a27d3758f367615ea48d4b635f984b3242303 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 3 Mar 2023 18:42:38 +0100 Subject: [PATCH 039/700] parent 9d5dc500e6c13e3923f13d3bc4231288d81846b8 author Sylvestre Ledru 1677865358 +0100 committer Sylvestre Ledru 1677951797 +0100 md: Fix a bunch of warnings in the docs --- CODE_OF_CONDUCT.md | 6 +- CONTRIBUTING.md | 9 +- DEVELOPER_INSTRUCTIONS.md | 40 ++++----- README.md | 118 +++++++++++++------------- docs/src/build.md | 2 +- docs/src/contributing.md | 4 +- docs/src/index.md | 4 + docs/src/installation.md | 13 ++- docs/src/multicall.md | 8 +- docs/src/test_coverage.md | 2 + src/uu/arch/arch.md | 3 - src/uu/base32/base32.md | 4 +- src/uu/base64/base64.md | 4 +- src/uu/chcon/chcon.md | 4 +- src/uu/cp/README.md | 6 +- src/uu/cut/BENCHMARKING.md | 33 ++++---- src/uu/dd/BENCHMARKING.md | 4 +- src/uu/dd/dd.md | 67 ++++++++------- src/uu/dircolors/README.md | 16 ++-- src/uu/du/du.md | 6 +- src/uu/expr/expr.md | 16 ++-- src/uu/factor/BENCHMARKING.md | 8 +- src/uu/hashsum/BENCHMARKING.md | 10 ++- src/uu/head/BENCHMARKING.md | 26 ++++-- src/uu/join/BENCHMARKING.md | 35 ++++---- src/uu/ls/BENCHMARKING.md | 11 ++- src/uu/mkdir/mkdir.md | 3 +- src/uu/numfmt/numfmt.md | 22 ++--- src/uu/realpath/realpath.md | 2 +- src/uu/seq/BENCHMARKING.md | 12 ++- src/uu/shuf/BENCHMARKING.md | 6 +- src/uu/shuf/shuf.md | 2 +- src/uu/sleep/sleep.md | 2 +- src/uu/sort/BENCHMARKING.md | 148 ++++++++++++++++----------------- src/uu/split/BENCHMARKING.md | 22 +++-- src/uu/split/README.md | 3 +- src/uu/sum/BENCHMARKING.md | 8 +- src/uu/tac/BENCHMARKING.md | 29 +++++-- src/uu/tail/README.md | 59 ++++++++----- src/uu/truncate/truncate.md | 3 +- src/uu/wc/BENCHMARKING.md | 59 +++++++++---- src/uu/wc/wc.md | 1 + 42 files changed, 473 insertions(+), 367 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index d196c6e9564..39474f7ab95 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -116,7 +116,7 @@ the community. This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at -https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. +. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). @@ -124,5 +124,5 @@ enforcement ladder](https://github.com/mozilla/diversity). [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see the FAQ at -https://www.contributor-covenant.org/faq. Translations are available at -https://www.contributor-covenant.org/translations. +. Translations are available at +. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7a749ebac40..f2e5763a1ba 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,20 +38,19 @@ search the issues to make sure no one else is working on it. ## Platforms -We take pride in supporting many operating systems and architectures. +We take pride in supporting many operating systems and architectures. **Tip:** -For Windows, Microsoft provides some images (VMWare, Hyper-V, VirtualBox and Parallels) +For Windows, Microsoft provides some images (VMWare, Hyper-V, VirtualBox and Parallels) for development: -https://developer.microsoft.com/windows/downloads/virtual-machines/ - + ## Commit messages To help the project maintainers review pull requests from contributors across numerous utilities, the team has settled on conventions for commit messages. -From http://git-scm.com/book/ch5-2.html: +From : ``` Short (50 chars or less) summary of changes diff --git a/DEVELOPER_INSTRUCTIONS.md b/DEVELOPER_INSTRUCTIONS.md index 28deb267777..f7dfc689ea5 100644 --- a/DEVELOPER_INSTRUCTIONS.md +++ b/DEVELOPER_INSTRUCTIONS.md @@ -1,21 +1,19 @@ -Documentation -------------- +# Documentation The source of the documentation is available on: -https://uutils.github.io/dev/coreutils/ + The documentation is updated everyday on this repository: -https://github.com/uutils/uutils.github.io/ + -Running GNU tests ------------------ +## Running GNU tests -- Check out https://github.com/coreutils/coreutils next to your fork as gnu -- Check out https://github.com/coreutils/gnulib next to your fork as gnulib +- Check out next to your fork as gnu +- Check out next to your fork as gnulib - Rename the checkout of your fork to uutils At the end you should have uutils, gnu and gnulib checked out next to each other. @@ -23,9 +21,7 @@ At the end you should have uutils, gnu and gnulib checked out next to each other - Run `cd uutils && ./util/build-gnu.sh && cd ..` to get everything ready (this may take a while) - Finally, you can run tests with `bash uutils/util/run-gnu-test.sh `. Instead of `` insert the tests you want to run, e.g. `tests/misc/wc-proc.sh`. - -Code Coverage Report Generation ---------------------------------- +## Code Coverage Report Generation @@ -36,13 +32,13 @@ Code coverage report can be generated using [grcov](https://github.com/mozilla/g To generate [gcov-based](https://github.com/mozilla/grcov#example-how-to-generate-gcda-files-for-a-rust-project) coverage report ```bash -$ export CARGO_INCREMENTAL=0 -$ export RUSTFLAGS="-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort" -$ export RUSTDOCFLAGS="-Cpanic=abort" -$ cargo build # e.g., --features feat_os_unix -$ cargo test # e.g., --features feat_os_unix test_pathchk -$ grcov . -s . --binary-path ./target/debug/ -t html --branch --ignore-not-existing --ignore build.rs --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?\#\[derive\()" -o ./target/debug/coverage/ -$ # open target/debug/coverage/index.html in browser +export CARGO_INCREMENTAL=0 +export RUSTFLAGS="-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort" +export RUSTDOCFLAGS="-Cpanic=abort" +cargo build # e.g., --features feat_os_unix +cargo test # e.g., --features feat_os_unix test_pathchk +grcov . -s . --binary-path ./target/debug/ -t html --branch --ignore-not-existing --ignore build.rs --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?\#\[derive\()" -o ./target/debug/coverage/ +# open target/debug/coverage/index.html in browser ``` if changes are not reflected in the report then run `cargo clean` and run the above commands. @@ -52,19 +48,17 @@ if changes are not reflected in the report then run `cargo clean` and run the ab If you are using stable version of Rust that doesn't enable code coverage instrumentation by default then add `-Z-Zinstrument-coverage` flag to `RUSTFLAGS` env variable specified above. - -pre-commit hooks ----------------- +## pre-commit hooks A configuration for `pre-commit` is provided in the repository. It allows automatically checking every git commit you make to ensure it compiles, and passes `clippy` and `rustfmt` without warnings. To use the provided hook: 1. [Install `pre-commit`](https://pre-commit.com/#install) -2. Run `pre-commit install` while in the repository directory +1. Run `pre-commit install` while in the repository directory Your git commits will then automatically be checked. If a check fails, an error message will explain why, and your commit will be canceled. You can then make the suggested changes, and run `git commit ...` again. -### Using Clippy +## Using Clippy The `msrv` key in the clippy configuration file `clippy.toml` is used to disable lints pertaining to newer features by specifying the minimum supported Rust version (MSRV). However, this key is only supported on `nightly`. To invoke clippy without errors, use `cargo +nightly clippy`. In order to also check tests and non-default crate features, use `cargo +nightly clippy --all-targets --all-features`. diff --git a/README.md b/README.md index 66a0395e960..1bd87401cbc 100644 --- a/README.md +++ b/README.md @@ -21,11 +21,12 @@ or different behavior might be experienced. To install it: -``` -$ cargo install coreutils -$ ~/.cargo/bin/coreutils +```bash +cargo install coreutils +~/.cargo/bin/coreutils ``` + ## Why? uutils aims to work on as many platforms as possible, to be able to use the @@ -35,6 +36,7 @@ chosen not only because it is fast and safe, but is also excellent for writing cross-platform code. ## Documentation + uutils has both user and developer documentation available: - [User Manual](https://uutils.github.io/user/) @@ -46,8 +48,8 @@ Both can also be generated locally, the instructions for that can be found in th ## Requirements -* Rust (`cargo`, `rustc`) -* GNU Make (optional) +- Rust (`cargo`, `rustc`) +- GNU Make (optional) ### Rust Version @@ -65,8 +67,8 @@ or GNU Make. For either method, we first need to fetch the repository: ```bash -$ git clone https://github.com/uutils/coreutils -$ cd coreutils +git clone https://github.com/uutils/coreutils +cd coreutils ``` ### Cargo @@ -75,7 +77,7 @@ Building uutils using Cargo is easy because the process is the same as for every other Rust program: ```bash -$ cargo build --release +cargo build --release ``` This command builds the most portable common core set of uutils into a multicall @@ -86,11 +88,11 @@ expanded sets of uutils for a platform (on that platform) is as simple as specifying it as a feature: ```bash -$ cargo build --release --features macos +cargo build --release --features macos # or ... -$ cargo build --release --features windows +cargo build --release --features windows # or ... -$ cargo build --release --features unix +cargo build --release --features unix ``` If you don't want to build every utility available on your platform into the @@ -98,7 +100,7 @@ final binary, you can also specify which ones you want to build manually. For example: ```bash -$ cargo build --features "base32 cat echo rm" --no-default-features +cargo build --features "base32 cat echo rm" --no-default-features ``` If you don't want to build the multicall binary and would prefer to build @@ -108,7 +110,7 @@ is contained in its own package within the main repository, named specific packages (using the `--package` [aka `-p`] option). For example: ```bash -$ cargo build -p uu_base32 -p uu_cat -p uu_echo -p uu_rm +cargo build -p uu_base32 -p uu_cat -p uu_echo -p uu_rm ``` ### GNU Make @@ -118,29 +120,29 @@ Building using `make` is a simple process as well. To simply build all available utilities: ```bash -$ make +make ``` To build all but a few of the available utilities: ```bash -$ make SKIP_UTILS='UTILITY_1 UTILITY_2' +make SKIP_UTILS='UTILITY_1 UTILITY_2' ``` To build only a few of the available utilities: ```bash -$ make UTILS='UTILITY_1 UTILITY_2' +make UTILS='UTILITY_1 UTILITY_2' ``` ## Installation -### Cargo +### Install with Cargo Likewise, installing can simply be done using: ```bash -$ cargo install --path . +cargo install --path . ``` This command will install uutils into Cargo's *bin* folder (*e.g.* `$HOME/.cargo/bin`). @@ -148,49 +150,49 @@ This command will install uutils into Cargo's *bin* folder (*e.g.* `$HOME/.cargo This does not install files necessary for shell completion. For shell completion to work, use `GNU Make` or see `Manually install shell completions`. -### GNU Make +### Install with GNU Make To install all available utilities: ```bash -$ make install +make install ``` To install using `sudo` switch `-E` must be used: ```bash -$ sudo -E make install +sudo -E make install ``` To install all but a few of the available utilities: ```bash -$ make SKIP_UTILS='UTILITY_1 UTILITY_2' install +make SKIP_UTILS='UTILITY_1 UTILITY_2' install ``` To install only a few of the available utilities: ```bash -$ make UTILS='UTILITY_1 UTILITY_2' install +make UTILS='UTILITY_1 UTILITY_2' install ``` To install every program with a prefix (e.g. uu-echo uu-cat): ```bash -$ make PROG_PREFIX=PREFIX_GOES_HERE install +make PROG_PREFIX=PREFIX_GOES_HERE install ``` To install the multicall binary: ```bash -$ make MULTICALL=y install +make MULTICALL=y install ``` Set install parent directory (default value is /usr/local): ```bash # DESTDIR is also supported -$ make PREFIX=/my/path install +make PREFIX=/my/path install ``` Installing with `make` installs shell completions for all installed utilities @@ -203,6 +205,7 @@ The `coreutils` binary can generate completions for the `bash`, `elvish`, `fish` and `zsh` shells. It prints the result to stdout. The syntax is: + ```bash cargo run completion ``` @@ -220,106 +223,107 @@ Un-installation differs depending on how you have installed uutils. If you used Cargo to install, use Cargo to uninstall. If you used GNU Make to install, use Make to uninstall. -### Cargo +### Uninstall with Cargo To uninstall uutils: ```bash -$ cargo uninstall uutils +cargo uninstall uutils ``` -### GNU Make +### Uninstall with GNU Make To uninstall all utilities: ```bash -$ make uninstall +make uninstall ``` To uninstall every program with a set prefix: ```bash -$ make PROG_PREFIX=PREFIX_GOES_HERE uninstall +make PROG_PREFIX=PREFIX_GOES_HERE uninstall ``` To uninstall the multicall binary: ```bash -$ make MULTICALL=y uninstall +make MULTICALL=y uninstall ``` To uninstall from a custom parent directory: ```bash # DESTDIR is also supported -$ make PREFIX=/my/path uninstall +make PREFIX=/my/path uninstall ``` + ## Testing Testing can be done using either Cargo or `make`. -### Cargo +### Testing with Cargo Just like with building, we follow the standard procedure for testing using Cargo: ```bash -$ cargo test +cargo test ``` By default, `cargo test` only runs the common programs. To run also platform specific tests, run: ```bash -$ cargo test --features unix +cargo test --features unix ``` If you would prefer to test a select few utilities: ```bash -$ cargo test --features "chmod mv tail" --no-default-features +cargo test --features "chmod mv tail" --no-default-features ``` If you also want to test the core utilities: ```bash -$ cargo test -p uucore -p coreutils +cargo test -p uucore -p coreutils ``` To debug: ```bash -$ gdb --args target/debug/coreutils ls +gdb --args target/debug/coreutils ls (gdb) b ls.rs:79 (gdb) run ``` -### GNU Make +### Testing with GNU Make To simply test all available utilities: ```bash -$ make test +make test ``` To test all but a few of the available utilities: ```bash -$ make SKIP_UTILS='UTILITY_1 UTILITY_2' test +make SKIP_UTILS='UTILITY_1 UTILITY_2' test ``` To test only a few of the available utilities: ```bash -$ make UTILS='UTILITY_1 UTILITY_2' test +make UTILS='UTILITY_1 UTILITY_2' test ``` To include tests for unimplemented behavior: ```bash -$ make UTILS='UTILITY_1 UTILITY_2' SPEC=y test +make UTILS='UTILITY_1 UTILITY_2' SPEC=y test ``` ### Run Busybox Tests @@ -330,19 +334,19 @@ requires `make`. To run busybox tests for all utilities for which busybox has tests ```bash -$ make busytest +make busytest ``` To run busybox tests for a few of the available utilities ```bash -$ make UTILS='UTILITY_1 UTILITY_2' busytest +make UTILS='UTILITY_1 UTILITY_2' busytest ``` To pass an argument like "-v" to the busybox test runtime ```bash -$ make UTILS='UTILITY_1 UTILITY_2' RUNTEST_ARGS='-v' busytest +make UTILS='UTILITY_1 UTILITY_2' RUNTEST_ARGS='-v' busytest ``` ### Comparing with GNU @@ -356,14 +360,14 @@ breakdown of the GNU test results of the main branch can be found To run locally: ```bash -$ bash util/build-gnu.sh -$ bash util/run-gnu-test.sh +bash util/build-gnu.sh +bash util/run-gnu-test.sh # To run a single test: -$ bash util/run-gnu-test.sh tests/touch/not-owner.sh # for example +bash util/run-gnu-test.sh tests/touch/not-owner.sh # for example # To run several tests: -$ bash util/run-gnu-test.sh tests/touch/not-owner.sh tests/rm/no-give-up.sh # for example +bash util/run-gnu-test.sh tests/touch/not-owner.sh tests/rm/no-give-up.sh # for example # If this is a perl (.pl) test, to run in debug: -$ DEBUG=1 bash util/run-gnu-test.sh tests/misc/sm3sum.pl +DEBUG=1 bash util/run-gnu-test.sh tests/misc/sm3sum.pl ``` Note that it relies on individual utilities (not the multicall binary). @@ -387,7 +391,6 @@ To improve the GNU compatibility, the following process is recommended: 1. Start to modify the Rust implementation to match the expected behavior 1. Add a test to make sure that we don't regress (our test suite is super quick) - ## Contributing To contribute to uutils, please see [CONTRIBUTING](CONTRIBUTING.md). @@ -395,11 +398,12 @@ To contribute to uutils, please see [CONTRIBUTING](CONTRIBUTING.md). ## Utilities Please note that this is not fully accurate: -* Some new options can be added / removed in the GNU implementation; -* Some error management might be missing; -* Some behaviors might be different. -See https://github.com/uutils/coreutils/issues/3336 for the main meta bugs +- Some new options can be added / removed in the GNU implementation; +- Some error management might be missing; +- Some behaviors might be different. + +See for the main meta bugs (many are missing). | Done | WIP | diff --git a/docs/src/build.md b/docs/src/build.md index 6505b5b6e97..e35b0ebe888 100644 --- a/docs/src/build.md +++ b/docs/src/build.md @@ -1,3 +1,3 @@ # Build from source -{{#include ../../README.md:build }} \ No newline at end of file +{{#include ../../README.md:build }} diff --git a/docs/src/contributing.md b/docs/src/contributing.md index 79ef4d13b5e..f69e1b3778f 100644 --- a/docs/src/contributing.md +++ b/docs/src/contributing.md @@ -1 +1,3 @@ -{{ #include ../../CONTRIBUTING.md }} \ No newline at end of file + + +{{ #include ../../CONTRIBUTING.md }} diff --git a/docs/src/index.md b/docs/src/index.md index 7bac8b8166a..0212b752659 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -1,5 +1,9 @@ + + {{#include logo.svg}} + +