Repository: uutils/coreutils (Rust)

Issue:
cp: -t overwrites data if called with a file, not a directory
```
printf '123' > file
printf '456' > dir
cp -t dir file && cmp dir file
```

`cp -t` expects two arguments: a directory to copy into, and a file to copy. If the first argument is a directory, uutils has the correct behavior. If the first argument is a file, GNU will detect this and fail with the message `cp: target 'dir' is not a directory`, but uutils will erase the data in `dir` and replace it with a copy of `file`. Effectively, uutils treats `cp -t first second` as equivalent to `cp second first` if `first` and `second` are both files. 


Discussion:
I created #3027 to address this. 

Using the example from the issue description:
```sh
$ printf 123 >file
$ printf 456 >dir
$ ./target/debug/coreutils cp -t dir file
error: Invalid value for '--target-directory <target-directory>': must specify a directory

For more information try --help
$ cmp file dir
file dir differ: byte 1, line 1
```



Patch:
diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs
index a4a512d6b5c..edd4f1bf245 100644
--- a/src/uu/cp/src/cp.rs
+++ b/src/uu/cp/src/cp.rs
@@ -315,6 +315,12 @@ pub fn uu_app<'a>() -> App<'a> {
              .long(options::TARGET_DIRECTORY)
              .value_name(options::TARGET_DIRECTORY)
              .takes_value(true)
+             .validator(|s| {
+                 if Path::new(s).is_dir() {
+                     return Ok(());
+                 }
+                 Err(format!("'{}' is not a directory", s))
+             })
              .help("copy all SOURCE arguments into target-directory"))
         .arg(Arg::new(options::NO_TARGET_DIRECTORY)
              .short('T')
@@ -469,7 +475,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
             LONG_HELP,
             backup_control::BACKUP_CONTROL_LONG_HELP
         ))
-        .get_matches_from(args);
+        .try_get_matches_from(args)?;
 
     let options = Options::from_matches(&matches)?;
 
diff --git a/src/uucore/src/lib/mods/error.rs b/src/uucore/src/lib/mods/error.rs
index ba7722f1c11..15d53233ed7 100644
--- a/src/uucore/src/lib/mods/error.rs
+++ b/src/uucore/src/lib/mods/error.rs
@@ -50,6 +50,7 @@
 
 // spell-checker:ignore uioerror
 
+use clap;
 use std::{
     error::Error,
     fmt::{Display, Formatter},
@@ -615,3 +616,13 @@ impl From<i32> for Box<dyn UError> {
         ExitCode::new(i)
     }
 }
+
+/// Implementations for clap::Error
+impl UError for clap::Error {
+    fn code(&self) -> i32 {
+        match self.kind {
+            clap::ErrorKind::DisplayHelp | clap::ErrorKind::DisplayVersion => 0,
+            _ => 1,
+        }
+    }
+}


Test patch:
diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs
index 9ee76bb5e45..11afa469e71 100644
--- a/tests/by-util/test_cp.rs
+++ b/tests/by-util/test_cp.rs
@@ -183,6 +183,16 @@ fn test_cp_arg_no_target_directory() {
         .stderr_contains("cannot overwrite directory");
 }
 
+#[test]
+fn test_cp_target_directory_is_file() {
+    new_ucmd!()
+        .arg("-t")
+        .arg(TEST_HOW_ARE_YOU_SOURCE)
+        .arg(TEST_HELLO_WORLD_SOURCE)
+        .fails()
+        .stderr_contains(format!("'{}' is not a directory", TEST_HOW_ARE_YOU_SOURCE));
+}
+
 #[test]
 fn test_cp_arg_interactive() {
     new_ucmd!()