diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 3fa99c871..6cc49c6cf 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -38,7 +38,7 @@ use libc::mkfifo; use quick_error::ResultExt; use uucore::backup_control::{self, BackupMode}; use uucore::display::Quotable; -use uucore::error::{set_exit_code, UClapError, UError, UResult, UUsageError}; +use uucore::error::{set_exit_code, UClapError, UError, UIoError, UResult, UUsageError}; use uucore::format_usage; use uucore::fs::{ canonicalize, paths_refer_to_same_file, FileInformation, MissingHandling, ResolveMode, @@ -1173,13 +1173,31 @@ fn copy_directory( }?; } } else { - copy_file( + // At this point, `path` is just a plain old file. + // Terminate this function immediately if there is any + // kind of error *except* a "permission denied" error. + // + // TODO What other kinds of errors, if any, should + // cause us to continue walking the directory? + match copy_file( path.as_path(), local_to_target.as_path(), options, symlinked_files, false, - )?; + ) { + Ok(_) => {} + Err(Error::IoErrContext(e, _)) + if e.kind() == std::io::ErrorKind::PermissionDenied => + { + show!(uio_error!( + e, + "cannot open {} for reading", + p.path().quote() + )); + } + Err(e) => return Err(e), + } } } } diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index e0b270e31..a87510798 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -2160,3 +2160,38 @@ fn test_copy_dir_preserve_permissions() { let metadata2 = at.metadata("d2"); assert_metadata_eq!(metadata1, metadata2); } + +/// Test for preserving permissions when copying a directory, even in +/// the face of an inaccessible file in that directory. +#[cfg(not(windows))] +#[test] +fn test_copy_dir_preserve_permissions_inaccessible_file() { + // Create a directory that has some non-default permissions and + // contains an inaccessible file. + let (at, mut ucmd) = at_and_ucmd!(); + at.mkdir("d1"); + at.touch("d1/f"); + at.set_mode("d1/f", 0); + at.set_mode("d1", 0o0500); + + // Copy the directory, preserving those permissions. There should + // be an error message that the file `d1/f` is inaccessible. + // + // preserve permissions (mode, ownership, timestamps) + // | copy directories recursively + // | | from this source directory + // | | | to this destination + // | | | | + // V V V V + ucmd.args(&["-p", "-R", "d1", "d2"]) + .fails() + .status_code(1) + .stderr_only("cp: cannot open 'd1/f' for reading: Permission denied"); + assert!(at.dir_exists("d2")); + assert!(!at.file_exists("d2/f")); + + // Assert that the permissions are preserved. + let metadata1 = at.metadata("d1"); + let metadata2 = at.metadata("d2"); + assert_metadata_eq!(metadata1, metadata2); +}