diff --git a/Cargo.lock b/Cargo.lock index 7ae0d078f..2ac0de37a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1212,9 +1212,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.4.5" +version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957056ecddbeba1b26965114e191d2e8589ce74db242b6ea25fc4062427a5c19" +checksum = "2a26af418b574bd56588335b3a3659a65725d4e636eb1016c2f9e3b38c7cc759" dependencies = [ "aho-corasick", "memchr 2.3.4", diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 4e245b298..da8c9037e 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -210,7 +210,6 @@ pub struct Options { overwrite: OverwriteMode, parents: bool, strip_trailing_slashes: bool, - reflink: bool, reflink_mode: ReflinkMode, preserve_attributes: Vec, recursive: bool, @@ -633,12 +632,12 @@ impl Options { update: matches.is_present(OPT_UPDATE), verbose: matches.is_present(OPT_VERBOSE), strip_trailing_slashes: matches.is_present(OPT_STRIP_TRAILING_SLASHES), - reflink: matches.is_present(OPT_REFLINK), reflink_mode: { if let Some(reflink) = matches.value_of(OPT_REFLINK) { match reflink { "always" => ReflinkMode::Always, "auto" => ReflinkMode::Auto, + "never" => ReflinkMode::Never, value => { return Err(Error::InvalidArgument(format!( "invalid argument '{}' for \'reflink\'", @@ -1196,7 +1195,7 @@ fn copy_file(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> { ///Copy the file from `source` to `dest` either using the normal `fs::copy` or the ///`FICLONE` ioctl if --reflink is specified and the filesystem supports it. fn copy_helper(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> { - if options.reflink { + if options.reflink_mode != ReflinkMode::Never { #[cfg(not(target_os = "linux"))] return Err("--reflink is only supported on linux".to_string().into()); diff --git a/src/uu/ls/src/quoting_style.rs b/src/uu/ls/src/quoting_style.rs index ceb54466c..49456fc22 100644 --- a/src/uu/ls/src/quoting_style.rs +++ b/src/uu/ls/src/quoting_style.rs @@ -1,6 +1,6 @@ use std::char::from_digit; -const SPECIAL_SHELL_CHARS: &str = "~`#$&*()\\|[]{};'\"<>?! "; +const SPECIAL_SHELL_CHARS: &str = "~`#$&*()|[]{};\\'\"<>?! "; pub(super) enum QuotingStyle { Shell { @@ -27,12 +27,10 @@ pub(super) enum Quotes { // This implementation is heavily inspired by the std::char::EscapeDefault implementation // in the Rust standard library. This custom implementation is needed because the // characters \a, \b, \e, \f & \v are not recognized by Rust. -#[derive(Clone, Debug)] struct EscapedChar { state: EscapeState, } -#[derive(Clone, Debug)] enum EscapeState { Done, Char(char), @@ -41,14 +39,12 @@ enum EscapeState { Octal(EscapeOctal), } -#[derive(Clone, Debug)] struct EscapeOctal { c: char, state: EscapeOctalState, idx: usize, } -#[derive(Clone, Debug)] enum EscapeOctalState { Done, Backslash, @@ -135,7 +131,6 @@ impl EscapedChar { '\x0B' => Backslash('v'), '\x0C' => Backslash('f'), '\r' => Backslash('r'), - '\\' => Backslash('\\'), '\x00'..='\x1F' | '\x7F' => Octal(EscapeOctal::from(c)), '\'' => match quotes { Quotes::Single => Backslash('\''), @@ -511,6 +506,23 @@ mod tests { ], ); + // A control character followed by a special shell character + check_names( + "one\n&two", + vec![ + ("one?&two", "literal"), + ("one\n&two", "literal-show"), + ("one\\n&two", "escape"), + ("\"one\\n&two\"", "c"), + ("'one?&two'", "shell"), + ("'one\n&two'", "shell-show"), + ("'one?&two'", "shell-always"), + ("'one\n&two'", "shell-always-show"), + ("'one'$'\\n''&two'", "shell-escape"), + ("'one'$'\\n''&two'", "shell-escape-always"), + ], + ); + // The first 16 control characters. NUL is also included, even though it is of // no importance for file names. check_names( @@ -627,4 +639,22 @@ mod tests { ], ); } + + #[test] + fn test_backslash() { + // Escaped in C-style, but not in Shell-style escaping + check_names( + "one\\two", + vec![ + ("one\\two", "literal"), + ("one\\two", "literal-show"), + ("one\\\\two", "escape"), + ("\"one\\\\two\"", "c"), + ("'one\\two'", "shell"), + ("\'one\\two\'", "shell-always"), + ("'one\\two'", "shell-escape"), + ("'one\\two'", "shell-escape-always"), + ], + ); + } } diff --git a/tests/by-util/test_cat.rs b/tests/by-util/test_cat.rs index 9f7ebdd37..c8ae29a9d 100644 --- a/tests/by-util/test_cat.rs +++ b/tests/by-util/test_cat.rs @@ -397,10 +397,10 @@ fn test_dev_full_show_all() { #[cfg(unix)] fn test_domain_socket() { use std::io::prelude::*; + use std::sync::{Arc, Barrier}; use std::thread; use tempdir::TempDir; use unix_socket::UnixListener; - use std::sync::{Barrier, Arc}; let dir = TempDir::new("unix_socket").expect("failed to create dir"); let socket_path = dir.path().join("sock"); diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 2d8a54c18..53c16e677 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -965,3 +965,59 @@ fn test_cp_one_file_system() { } } } + +#[test] +#[cfg(target_os = "linux")] +fn test_cp_reflink_always() { + let (at, mut ucmd) = at_and_ucmd!(); + let result = ucmd + .arg("--reflink=always") + .arg(TEST_HELLO_WORLD_SOURCE) + .arg(TEST_EXISTING_FILE) + .run(); + + if result.succeeded() { + // Check the content of the destination file + assert_eq!(at.read(TEST_EXISTING_FILE), "Hello, World!\n"); + } else { + // Older Linux versions do not support cloning. + } +} + +#[test] +#[cfg(target_os = "linux")] +fn test_cp_reflink_auto() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.arg("--reflink=auto") + .arg(TEST_HELLO_WORLD_SOURCE) + .arg(TEST_EXISTING_FILE) + .succeeds(); + + // Check the content of the destination file + assert_eq!(at.read(TEST_EXISTING_FILE), "Hello, World!\n"); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_cp_reflink_never() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.arg("--reflink=never") + .arg(TEST_HELLO_WORLD_SOURCE) + .arg(TEST_EXISTING_FILE) + .succeeds(); + + // Check the content of the destination file + assert_eq!(at.read(TEST_EXISTING_FILE), "Hello, World!\n"); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_cp_reflink_bad() { + let (_, mut ucmd) = at_and_ucmd!(); + let _result = ucmd + .arg("--reflink=bad") + .arg(TEST_HELLO_WORLD_SOURCE) + .arg(TEST_EXISTING_FILE) + .fails() + .stderr_contains("invalid argument"); +} diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index f87e64688..09e02f264 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -104,6 +104,12 @@ fn test_ls_width() { .stdout_only("test-width-1\ntest-width-2\ntest-width-3\ntest-width-4\n"); } + scene + .ucmd() + .arg("-w=bad") + .fails() + .stderr_contains("invalid line width"); + for option in &["-w 1a", "-w=1a", "--width=1a", "--width 1a"] { scene .ucmd() @@ -444,6 +450,39 @@ fn test_ls_deref() { assert!(!re.is_match(result.stdout_str().trim())); } +#[test] +fn test_ls_sort_none() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.touch("test-3"); + at.touch("test-1"); + at.touch("test-2"); + + // Order is not specified so we just check that it doesn't + // give any errors. + scene.ucmd().arg("--sort=none").succeeds(); + scene.ucmd().arg("-U").succeeds(); +} + +#[test] +fn test_ls_sort_name() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.touch("test-3"); + at.touch("test-1"); + at.touch("test-2"); + + let sep = if cfg!(unix) { "\n" } else { " " }; + + scene + .ucmd() + .arg("--sort=name") + .succeeds() + .stdout_is(["test-1", "test-2", "test-3\n"].join(sep)); +} + #[test] fn test_ls_order_size() { let scene = TestScenario::new(util_name!()); @@ -472,6 +511,18 @@ fn test_ls_order_size() { result.stdout_only("test-1\ntest-2\ntest-3\ntest-4\n"); #[cfg(windows)] result.stdout_only("test-1 test-2 test-3 test-4\n"); + + let result = scene.ucmd().arg("--sort=size").succeeds(); + #[cfg(not(windows))] + result.stdout_only("test-4\ntest-3\ntest-2\ntest-1\n"); + #[cfg(windows)] + result.stdout_only("test-4 test-3 test-2 test-1\n"); + + let result = scene.ucmd().arg("--sort=size").arg("-r").succeeds(); + #[cfg(not(windows))] + result.stdout_only("test-1\ntest-2\ntest-3\ntest-4\n"); + #[cfg(windows)] + result.stdout_only("test-1 test-2 test-3 test-4\n"); } #[test] @@ -480,13 +531,16 @@ fn test_ls_long_ctime() { let at = &scene.fixtures; at.touch("test-long-ctime-1"); - let result = scene.ucmd().arg("-lc").succeeds(); - // Should show the time on Unix, but question marks on windows. - #[cfg(unix)] - result.stdout_contains(":"); - #[cfg(not(unix))] - result.stdout_contains("???"); + for arg in &["-c", "--time=ctime", "--time=status"] { + let result = scene.ucmd().arg("-l").arg(arg).succeeds(); + + // Should show the time on Unix, but question marks on windows. + #[cfg(unix)] + result.stdout_contains(":"); + #[cfg(not(unix))] + result.stdout_contains("???"); + } } #[test] @@ -527,32 +581,46 @@ fn test_ls_order_time() { #[cfg(windows)] result.stdout_only("test-4 test-3 test-2 test-1\n"); + let result = scene.ucmd().arg("--sort=time").succeeds(); + #[cfg(not(windows))] + result.stdout_only("test-4\ntest-3\ntest-2\ntest-1\n"); + #[cfg(windows)] + result.stdout_only("test-4 test-3 test-2 test-1\n"); + let result = scene.ucmd().arg("-tr").succeeds(); #[cfg(not(windows))] result.stdout_only("test-1\ntest-2\ntest-3\ntest-4\n"); #[cfg(windows)] result.stdout_only("test-1 test-2 test-3 test-4\n"); + let result = scene.ucmd().arg("--sort=time").arg("-r").succeeds(); + #[cfg(not(windows))] + result.stdout_only("test-1\ntest-2\ntest-3\ntest-4\n"); + #[cfg(windows)] + result.stdout_only("test-1 test-2 test-3 test-4\n"); + // 3 was accessed last in the read // So the order should be 2 3 4 1 - let result = scene.ucmd().arg("-tu").succeeds(); - let file3_access = at.open("test-3").metadata().unwrap().accessed().unwrap(); - let file4_access = at.open("test-4").metadata().unwrap().accessed().unwrap(); + for arg in &["-u", "--time=atime", "--time=access", "--time=use"] { + let result = scene.ucmd().arg("-t").arg(arg).succeeds(); + let file3_access = at.open("test-3").metadata().unwrap().accessed().unwrap(); + let file4_access = at.open("test-4").metadata().unwrap().accessed().unwrap(); - // It seems to be dependent on the platform whether the access time is actually set - if file3_access > file4_access { - if cfg!(not(windows)) { - result.stdout_only("test-3\ntest-4\ntest-2\ntest-1\n"); + // It seems to be dependent on the platform whether the access time is actually set + if file3_access > file4_access { + if cfg!(not(windows)) { + result.stdout_only("test-3\ntest-4\ntest-2\ntest-1\n"); + } else { + result.stdout_only("test-3 test-4 test-2 test-1\n"); + } } else { - result.stdout_only("test-3 test-4 test-2 test-1\n"); - } - } else { - // Access time does not seem to be set on Windows and some other - // systems so the order is 4 3 2 1 - if cfg!(not(windows)) { - result.stdout_only("test-4\ntest-3\ntest-2\ntest-1\n"); - } else { - result.stdout_only("test-4 test-3 test-2 test-1\n"); + // Access time does not seem to be set on Windows and some other + // systems so the order is 4 3 2 1 + if cfg!(not(windows)) { + result.stdout_only("test-4\ntest-3\ntest-2\ntest-1\n"); + } else { + result.stdout_only("test-4 test-3 test-2 test-1\n"); + } } } @@ -1059,9 +1127,11 @@ fn test_ls_quoting_style() { at.touch("one"); // It seems that windows doesn't allow \n in filenames. + // And it also doesn't like \, of course. #[cfg(unix)] { at.touch("one\ntwo"); + at.touch("one\\two"); // Default is shell-escape scene .ucmd() @@ -1123,6 +1193,42 @@ fn test_ls_quoting_style() { .succeeds() .stdout_only(format!("{}\n", correct)); } + + for (arg, correct) in &[ + ("--quoting-style=literal", "one\\two"), + ("-N", "one\\two"), + ("--quoting-style=c", "\"one\\\\two\""), + ("-Q", "\"one\\\\two\""), + ("--quote-name", "\"one\\\\two\""), + ("--quoting-style=escape", "one\\\\two"), + ("-b", "one\\\\two"), + ("--quoting-style=shell-escape", "'one\\two'"), + ("--quoting-style=shell-escape-always", "'one\\two'"), + ("--quoting-style=shell", "'one\\two'"), + ("--quoting-style=shell-always", "'one\\two'"), + ] { + scene + .ucmd() + .arg(arg) + .arg("one\\two") + .succeeds() + .stdout_only(format!("{}\n", correct)); + } + + // Tests for a character that forces quotation in shell-style escaping + // after a character in a dollar expression + at.touch("one\n&two"); + for (arg, correct) in &[ + ("--quoting-style=shell-escape", "'one'$'\\n''&two'"), + ("--quoting-style=shell-escape-always", "'one'$'\\n''&two'"), + ] { + scene + .ucmd() + .arg(arg) + .arg("one\n&two") + .succeeds() + .stdout_only(format!("{}\n", correct)); + } } scene @@ -1323,6 +1429,43 @@ fn test_ls_ignore_hide() { .stdout_is("CONTRIBUTING.md\nREADME.md\nREADMECAREFULLY.md\nsome_other_file\n"); } +#[test] +fn test_ls_ignore_backups() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.touch("somefile"); + at.touch("somebackup~"); + at.touch(".somehiddenfile"); + at.touch(".somehiddenbackup~"); + + scene.ucmd().arg("-B").succeeds().stdout_is("somefile\n"); + scene + .ucmd() + .arg("--ignore-backups") + .succeeds() + .stdout_is("somefile\n"); + + scene + .ucmd() + .arg("-aB") + .succeeds() + .stdout_contains(".somehiddenfile") + .stdout_contains("somefile") + .stdout_does_not_contain("somebackup") + .stdout_does_not_contain(".somehiddenbackup~"); + + scene + .ucmd() + .arg("-a") + .arg("--ignore-backups") + .succeeds() + .stdout_contains(".somehiddenfile") + .stdout_contains("somefile") + .stdout_does_not_contain("somebackup") + .stdout_does_not_contain(".somehiddenbackup~"); +} + #[test] fn test_ls_directory() { let scene = TestScenario::new(util_name!()); diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 0c4639fa0..a7a8c9b5f 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -612,8 +612,8 @@ fn test_dictionary_and_nonprinting_conflicts() { #[test] fn test_trailing_separator() { new_ucmd!() - .args(&["-t", "x", "-k", "1,1"]) - .pipe_in("aax\naaa\n") - .succeeds() - .stdout_is("aax\naaa\n"); + .args(&["-t", "x", "-k", "1,1"]) + .pipe_in("aax\naaa\n") + .succeeds() + .stdout_is("aax\naaa\n"); } diff --git a/tests/by-util/test_tail.rs b/tests/by-util/test_tail.rs index 1f74a3a98..1c025cf4c 100644 --- a/tests/by-util/test_tail.rs +++ b/tests/by-util/test_tail.rs @@ -346,9 +346,5 @@ fn test_negative_indexing() { #[test] fn test_sleep_interval() { - new_ucmd!() - .arg("-s") - .arg("10") - .arg(FOOBAR_TXT) - .succeeds(); + new_ucmd!().arg("-s").arg("10").arg(FOOBAR_TXT).succeeds(); } diff --git a/tests/by-util/test_truncate.rs b/tests/by-util/test_truncate.rs index 2a1f4429b..64573f2c0 100644 --- a/tests/by-util/test_truncate.rs +++ b/tests/by-util/test_truncate.rs @@ -79,4 +79,3 @@ fn test_failed_incorrect_arg() { let (_at, mut ucmd) = at_and_ucmd!(); ucmd.args(&["-s", "+5A", TFILE1]).fails(); } -