Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 24 additions & 9 deletions src/uu/cp/src/copydir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ use crate::{
copy_file,
};

/// Represents a directory that needs permission fixup after copying its contents.
struct DirNeedingPermissions {
/// Absolute path to the source directory
source: PathBuf,
/// Path to the destination directory
dest: PathBuf,
/// Whether this directory was freshly created by the copy operation
was_created: bool,
}

/// Ensure a Windows path starts with a `\\?`.
#[cfg(target_os = "windows")]
fn adjust_canonicalization(p: &Path) -> Cow<'_, Path> {
Expand Down Expand Up @@ -237,7 +247,12 @@ impl Entry {

#[allow(clippy::too_many_arguments)]
/// Copy a single entry during a directory traversal.
/// Returns a bool value indicating whether this function created a directory or not
///
/// # Returns
///
/// Returns `Ok(true)` if this function created a new directory, `Ok(false)` otherwise.
/// This information is used to determine whether default directory permissions should
/// be preserved during attribute copying.
fn copy_direntry(
progress_bar: Option<&ProgressBar>,
entry: &Entry,
Expand Down Expand Up @@ -422,7 +437,7 @@ pub(crate) fn copy_directory(
let mut last_iter: Option<DirEntry> = None;

// Keep track of all directories we've created that need permission fixes
let mut dirs_needing_permissions: Vec<(PathBuf, PathBuf, bool)> = Vec::new();
let mut dirs_needing_permissions: Vec<DirNeedingPermissions> = Vec::new();

// Traverse the contents of the directory, copying each one.
for direntry_result in WalkDir::new(root)
Expand Down Expand Up @@ -481,11 +496,11 @@ pub(crate) fn copy_directory(
continue;
}
// Add this directory to our list for permission fixing later
dirs_needing_permissions.push((
entry.source_absolute.clone(),
entry.local_to_target.clone(),
created,
));
dirs_needing_permissions.push(DirNeedingPermissions {
source: entry.source_absolute.clone(),
dest: entry.local_to_target.clone(),
was_created: created,
});

// If true, last_iter is not a parent of this iter.
// The means we just exited a directory.
Expand Down Expand Up @@ -534,8 +549,8 @@ pub(crate) fn copy_directory(

// Fix permissions for all directories we created
// This ensures that even sibling directories get their permissions fixed
for (source_path, dest_path, created) in dirs_needing_permissions {
copy_attributes(&source_path, &dest_path, &options.attributes, created)?;
for dir in dirs_needing_permissions {
copy_attributes(&dir.source, &dir.dest, &options.attributes, dir.was_created)?;
}

// Also fix permissions for parent directories,
Expand Down
6 changes: 3 additions & 3 deletions src/uu/cp/src/cp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1717,16 +1717,16 @@ pub(crate) fn copy_attributes(
source: &Path,
dest: &Path,
attributes: &Attributes,
created: bool,
dest_is_freshly_created_dir: bool,
) -> CopyResult<()> {
let context = &*format!("{} -> {}", source.quote(), dest.quote());
let source_metadata =
fs::symlink_metadata(source).map_err(|e| CpError::IoErrContext(e, context.to_owned()))?;

let is_explicit = matches!(attributes.mode, Preserve::No { explicit: true });
let mode_explicitly_disabled = matches!(attributes.mode, Preserve::No { explicit: true });

// preserve is true by default if the destination is created by us and it's a directory
let mode = if !is_explicit && dest.is_dir() && created {
let mode = if !mode_explicitly_disabled && dest_is_freshly_created_dir {
Preserve::Yes { required: false }
} else {
attributes.mode
Expand Down
Loading