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
102 changes: 71 additions & 31 deletions src/uu/cp/src/copydir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ struct Context<'a> {
/// The target path to which the directory will be copied.
target: &'a Path,

/// Whether the target is an existing file. Cached to avoid repeated `stat` calls.
target_is_file: bool,

/// The source path from which the directory will be copied.
root: &'a Path,
}
Expand All @@ -107,6 +110,7 @@ impl<'a> Context<'a> {
fn new(root: &'a Path, target: &'a Path) -> io::Result<Self> {
let current_dir = env::current_dir()?;
let root_path = current_dir.join(root);
let target_is_file = target.is_file();
let root_parent = if target.exists() && !root.to_str().unwrap().ends_with("/.") {
root_path.parent().map(|p| p.to_path_buf())
} else if root == Path::new(".") && target.is_dir() {
Expand All @@ -122,6 +126,7 @@ impl<'a> Context<'a> {
current_dir,
root_parent,
target,
target_is_file,
root,
})
}
Expand Down Expand Up @@ -213,7 +218,7 @@ impl Entry {
}

let local_to_target = context.target.join(descendant);
let target_is_file = context.target.is_file();
let target_is_file = context.target_is_file;
Ok(Self {
source_absolute,
source_relative,
Expand All @@ -227,54 +232,73 @@ impl Entry {
/// Copy a single entry during a directory traversal.
fn copy_direntry(
progress_bar: Option<&ProgressBar>,
entry: Entry,
entry: &Entry,
entry_is_symlink: bool,
entry_is_dir_no_follow: bool,
options: &Options,
symlinked_files: &mut HashSet<FileInformation>,
preserve_hard_links: bool,
copied_destinations: &HashSet<PathBuf>,
copied_files: &mut HashMap<FileInformation, PathBuf>,
created_parent_dirs: &mut HashSet<PathBuf>,
) -> CopyResult<()> {
let Entry {
source_absolute,
source_relative,
local_to_target,
target_is_file,
} = entry;
let source_is_symlink = entry_is_symlink;
let source_is_dir = if source_is_symlink && !options.dereference {
false
} else if source_is_symlink {
entry.source_absolute.is_dir()
} else {
entry_is_dir_no_follow
};

// If the source is a symbolic link and the options tell us not to
// dereference the link, then copy the link object itself.
if source_absolute.is_symlink() && !options.dereference {
return copy_link(&source_absolute, &local_to_target, symlinked_files, options);
if source_is_symlink && !options.dereference {
return copy_link(
&entry.source_absolute,
&entry.local_to_target,
symlinked_files,
options,
);
}

// If the source is a directory and the destination does not
// exist, ...
if source_absolute.is_dir() && !local_to_target.exists() {
return if target_is_file {
if source_is_dir && !entry.local_to_target.exists() {
return if entry.target_is_file {
Err(translate!("cp-error-cannot-overwrite-non-directory-with-directory").into())
} else {
build_dir(&local_to_target, false, options, Some(&source_absolute))?;
build_dir(
&entry.local_to_target,
false,
options,
Some(&entry.source_absolute),
)?;
if options.verbose {
println!("{}", context_for(&source_relative, &local_to_target));
println!(
"{}",
context_for(&entry.source_relative, &entry.local_to_target)
);
}
Ok(())
};
}

// If the source is not a directory, then we need to copy the file.
if !source_absolute.is_dir() {
if !source_is_dir {
if let Err(err) = copy_file(
progress_bar,
&source_absolute,
local_to_target.as_path(),
&entry.source_absolute,
entry.local_to_target.as_path(),
options,
symlinked_files,
copied_destinations,
copied_files,
created_parent_dirs,
false,
) {
if preserve_hard_links {
if !source_absolute.is_symlink() {
if !source_is_symlink {
return Err(err);
}
// silent the error with a symlink
Expand All @@ -292,7 +316,10 @@ fn copy_direntry(
show!(uio_error!(
e,
"{}",
translate!("cp-error-cannot-open-for-reading", "source" => source_relative.quote()),
translate!(
"cp-error-cannot-open-for-reading",
"source" => entry.source_relative.quote()
),
));
}
e => return Err(e),
Expand Down Expand Up @@ -320,6 +347,7 @@ pub(crate) fn copy_directory(
symlinked_files: &mut HashSet<FileInformation>,
copied_destinations: &HashSet<PathBuf>,
copied_files: &mut HashMap<FileInformation, PathBuf>,
created_parent_dirs: &mut HashSet<PathBuf>,
source_in_command_line: bool,
) -> CopyResult<()> {
// if no-dereference is enabled and this is a symlink, copy it as a file
Expand All @@ -332,6 +360,7 @@ pub(crate) fn copy_directory(
symlinked_files,
copied_destinations,
copied_files,
created_parent_dirs,
source_in_command_line,
);
}
Expand Down Expand Up @@ -405,16 +434,29 @@ pub(crate) fn copy_directory(
{
match direntry_result {
Ok(direntry) => {
let entry = Entry::new(&context, direntry.path(), options.no_target_dir)?;
let direntry_type = direntry.file_type();
let direntry_path = direntry.path();
let (entry_is_symlink, entry_is_dir_no_follow) =
match direntry_path.symlink_metadata() {
Ok(metadata) => {
let file_type = metadata.file_type();
(file_type.is_symlink(), file_type.is_dir())
}
Err(_) => (direntry_type.is_symlink(), direntry_type.is_dir()),
};
let entry = Entry::new(&context, direntry_path, options.no_target_dir)?;

copy_direntry(
progress_bar,
entry,
&entry,
entry_is_symlink,
entry_is_dir_no_follow,
options,
symlinked_files,
preserve_hard_links,
copied_destinations,
copied_files,
created_parent_dirs,
)?;

// We omit certain permissions when creating directories
Expand All @@ -428,19 +470,17 @@ pub(crate) fn copy_directory(
// (Note that there can be more than one! We might step out of
// `./a/b/c` into `./a/`, in which case we'll need to fix the
// permissions of both `./a/b/c` and `./a/b`, in that order.)
if direntry.file_type().is_dir() {
let is_dir_for_permissions =
entry_is_dir_no_follow || (options.dereference && direntry_path.is_dir());
if is_dir_for_permissions {
// Add this directory to our list for permission fixing later
let entry_for_tracking =
Entry::new(&context, direntry.path(), options.no_target_dir)?;
dirs_needing_permissions.push((
entry_for_tracking.source_absolute,
entry_for_tracking.local_to_target,
));
dirs_needing_permissions
.push((entry.source_absolute.clone(), entry.local_to_target.clone()));

// If true, last_iter is not a parent of this iter.
// The means we just exited a directory.
let went_up = if let Some(last_iter) = &last_iter {
last_iter.path().strip_prefix(direntry.path()).is_ok()
last_iter.path().strip_prefix(direntry_path).is_ok()
} else {
false
};
Expand All @@ -454,14 +494,14 @@ pub(crate) fn copy_directory(
//
// All the unwraps() here are unreachable.
let last_iter = last_iter.as_ref().unwrap();
let diff = last_iter.path().strip_prefix(direntry.path()).unwrap();
let diff = last_iter.path().strip_prefix(direntry_path).unwrap();

// Fix permissions for every entry in `diff`, inside-out.
// We skip the last directory (which will be `.`) because
// its permissions will be fixed when we walk _out_ of it.
// (at this point, we might not be done copying `.`!)
for p in skip_last(diff.ancestors()) {
let src = direntry.path().join(p);
let src = direntry_path.join(p);
let entry = Entry::new(&context, &src, options.no_target_dir)?;

copy_attributes(
Expand Down
Loading
Loading