diff --git a/crates/nu-command/src/filesystem/cp.rs b/crates/nu-command/src/filesystem/cp.rs index 687b5f278c..4e6aa422a3 100644 --- a/crates/nu-command/src/filesystem/cp.rs +++ b/crates/nu-command/src/filesystem/cp.rs @@ -351,17 +351,38 @@ fn copy_file(src: PathBuf, dst: PathBuf, span: Span) -> Value { Value::String { val: msg, span } } Err(e) => { - let message = format!("copy file {src:?} failed: {e}"); - + let message_src = format!( + "copying file '{src_display}' failed: {e}", + src_display = src.display() + ); + let message_dst = format!( + "copying to destination '{dst_display}' failed: {e}", + dst_display = dst.display() + ); use std::io::ErrorKind; let shell_error = match e.kind() { - ErrorKind::NotFound => ShellError::FileNotFoundCustom(message, span), - ErrorKind::PermissionDenied => ShellError::PermissionDeniedError(message, span), - ErrorKind::Interrupted => ShellError::IOInterrupted(message, span), - ErrorKind::OutOfMemory => ShellError::OutOfMemoryError(message, span), + ErrorKind::NotFound => { + if std::path::Path::new(&dst).exists() { + ShellError::FileNotFoundCustom(message_src, span) + } else { + ShellError::FileNotFoundCustom(message_dst, span) + } + } + ErrorKind::PermissionDenied => match std::fs::metadata(&dst) { + Ok(meta) => { + if meta.permissions().readonly() { + ShellError::PermissionDeniedError(message_dst, span) + } else { + ShellError::PermissionDeniedError(message_src, span) + } + } + Err(_) => ShellError::PermissionDeniedError(message_dst, span), + }, + ErrorKind::Interrupted => ShellError::IOInterrupted(message_src, span), + ErrorKind::OutOfMemory => ShellError::OutOfMemoryError(message_src, span), // TODO: handle ExecutableFileBusy etc. when io_error_more is stabilized // https://github.com/rust-lang/rust/issues/86442 - _ => ShellError::IOErrorSpanned(message, span), + _ => ShellError::IOErrorSpanned(message_src, span), }; Value::Error { error: shell_error } diff --git a/crates/nu-command/tests/commands/cp.rs b/crates/nu-command/tests/commands/cp.rs index 5d0a5bb7f0..e10d961fbf 100644 --- a/crates/nu-command/tests/commands/cp.rs +++ b/crates/nu-command/tests/commands/cp.rs @@ -1,5 +1,8 @@ use nu_test_support::fs::file_contents; -use nu_test_support::fs::{files_exist_at, AbsoluteFile, Stub::EmptyFile}; +use nu_test_support::fs::{ + files_exist_at, AbsoluteFile, + Stub::{EmptyFile, FileWithPermission}, +}; use nu_test_support::nu; use nu_test_support::playground::Playground; use std::path::Path; @@ -344,3 +347,37 @@ fn copy_ignores_ansi() { assert_eq!(actual.out, "success.txt"); }); } + +#[test] +fn copy_file_not_exists_dst() { + Playground::setup("cp_test_17", |_dirs, sandbox| { + sandbox.with_files(vec![EmptyFile("valid.txt")]); + + let actual = nu!( + cwd: sandbox.cwd(), + "cp valid.txt ~/invalid_dir/invalid_dir1" + ); + assert!( + actual.err.contains("invalid_dir1") && actual.err.contains("copying to destination") + ); + }); +} + +#[test] +fn copy_file_with_read_permission() { + Playground::setup("cp_test_18", |_dirs, sandbox| { + sandbox.with_files(vec![ + EmptyFile("valid.txt"), + FileWithPermission("invalid_prem.txt", false), + ]); + + let actual = nu!( + cwd: sandbox.cwd(), + "cp valid.txt invalid_prem.txt", + ); + assert!( + actual.err.contains("invalid_prem.txt") + && actual.err.contains("copying to destination") + ); + }); +} diff --git a/crates/nu-test-support/src/fs.rs b/crates/nu-test-support/src/fs.rs index be6b5c2bf6..2259d13760 100644 --- a/crates/nu-test-support/src/fs.rs +++ b/crates/nu-test-support/src/fs.rs @@ -137,6 +137,7 @@ pub enum Stub<'a> { FileWithContent(&'a str, &'a str), FileWithContentToBeTrimmed(&'a str, &'a str), EmptyFile(&'a str), + FileWithPermission(&'a str, bool), } pub fn file_contents(full_path: impl AsRef) -> String { diff --git a/crates/nu-test-support/src/playground/play.rs b/crates/nu-test-support/src/playground/play.rs index b84f6d58bb..6fdb3334fe 100644 --- a/crates/nu-test-support/src/playground/play.rs +++ b/crates/nu-test-support/src/playground/play.rs @@ -202,7 +202,8 @@ impl<'a> Playground<'a> { .iter() .map(|f| { let mut path = PathBuf::from(&self.cwd); - + let mut permission_set = false; + let mut write_able = true; let (file_name, contents) = match *f { Stub::EmptyFile(name) => (name, "fake data".to_string()), Stub::FileWithContent(name, content) => (name, content.to_string()), @@ -215,11 +216,24 @@ impl<'a> Playground<'a> { .collect::>() .join(&endl), ), + Stub::FileWithPermission(name, is_write_able) => { + permission_set = true; + write_able = is_write_able; + (name, "check permission".to_string()) + } }; path.push(file_name); - std::fs::write(path, contents.as_bytes()).expect("can not create file"); + std::fs::write(&path, contents.as_bytes()).expect("can not create file"); + if permission_set { + let err_perm = "can not set permission"; + let mut perm = std::fs::metadata(path.clone()) + .expect(err_perm) + .permissions(); + perm.set_readonly(!write_able); + std::fs::set_permissions(path, perm).expect(err_perm); + } }) .for_each(drop); self.back_to_playground();