Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cp: correctly copy attributes of a dangling symbolic link #3692

Merged
merged 6 commits into from
Jul 11, 2022
Merged
Show file tree
Hide file tree
Changes from 2 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
34 changes: 16 additions & 18 deletions src/uu/cp/src/cp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -983,6 +983,16 @@ fn adjust_canonicalization(p: &Path) -> Cow<Path> {
}
}

/// Decide whether the given path is a symbolic link.
fn is_symlink(path: &Path) -> bool {
// TODO Replace this convenience function with `Path::is_symlink()`
// when the minimum supported version of Rust is 1.58 or greater.
match fs::symlink_metadata(path) {
Err(_) => false,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we need to call file_type().is_symlink() on the value stored in Ok(). Could you give an example of what you mean and why it would work better?

Copy link
Contributor

@anastygnome anastygnome Jul 4, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this is a case for a map.

fs::symlink_metadata(path)
        .map(|m| m.file_type().is_symlink()) 
        .unwrap_or(false)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe try with map_or ?

fs::symlink_metadata(path).map_or(|m| m.file_type().is_symlink(), false)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have pulled the suggested refactor out into a separate pull request, which should be merged before this one. See #3697

Ok(m) => m.file_type().is_symlink(),
}
}

/// Read the contents of the directory `root` and recursively copy the
/// contents to `target`.
///
Expand All @@ -999,9 +1009,7 @@ fn copy_directory(
}

// if no-dereference is enabled and this is a symlink, copy it as a file
if !options.dereference && fs::symlink_metadata(root).unwrap().file_type().is_symlink()
// replace by is_symlink in rust>=1.58
{
if !options.dereference && is_symlink(root) {
return copy_file(root, target, options, symlinked_files);
}

Expand Down Expand Up @@ -1044,8 +1052,6 @@ fn copy_directory(
.follow_links(options.dereference)
{
let p = or_continue!(path);
let is_symlink = fs::symlink_metadata(p.path())?.file_type().is_symlink();
// replace by is_symlink in rust >=1.58
let path = current_dir.join(&p.path());

let local_to_root_parent = match root_parent {
Expand All @@ -1069,7 +1075,7 @@ fn copy_directory(
};

let local_to_target = target.join(&local_to_root_parent);
if is_symlink && !options.dereference {
if is_symlink(&path) && !options.dereference {
copy_link(&path, &local_to_target, symlinked_files)?;
} else if path.is_dir() && !local_to_target.exists() {
if target.is_file() {
Expand All @@ -1091,7 +1097,7 @@ fn copy_directory(
) {
Ok(_) => Ok(()),
Err(err) => {
if fs::symlink_metadata(&source)?.file_type().is_symlink() {
if is_symlink(&source) {
// silent the error with a symlink
// In case we do --archive, we might copy the symlink
// before the file itself
Expand Down Expand Up @@ -1306,10 +1312,7 @@ fn copy_file(
}

// Fail if dest is a dangling symlink or a symlink this program created previously
if fs::symlink_metadata(dest)
.map(|m| m.file_type().is_symlink()) // replace by is_symlink in rust>=1.58
.unwrap_or(false)
{
if is_symlink(dest) {
if FileInformation::from_path(dest, false)
.map(|info| symlinked_files.contains(&info))
.unwrap_or(false)
Expand Down Expand Up @@ -1351,9 +1354,7 @@ fn copy_file(
#[cfg(not(unix))]
let source_is_fifo = false;

let dest_already_exists_as_symlink = fs::symlink_metadata(&dest)
.map(|meta| meta.file_type().is_symlink())
.unwrap_or(false);
let dest_already_exists_as_symlink = is_symlink(dest);

let dest = if !(source_is_symlink && dest_already_exists_as_symlink) {
canonicalize(dest, MissingHandling::Missing, ResolveMode::Physical).unwrap()
Expand Down Expand Up @@ -1457,10 +1458,7 @@ fn copy_file(
};

// TODO: implement something similar to gnu's lchown
if fs::symlink_metadata(&dest)
.map(|meta| !meta.file_type().is_symlink())
.unwrap_or(false)
{
if !is_symlink(&dest) {
// Here, to match GNU semantics, we quietly ignore an error
// if a user does not have the correct ownership to modify
// the permissions of a file.
Expand Down
8 changes: 8 additions & 0 deletions tests/common/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,14 @@ impl AtPath {
}
}

/// Decide whether the named symbolic link exists in the test directory.
pub fn symlink_exists(&self, path: &str) -> bool {
match fs::symlink_metadata(&self.plus(path)) {
Ok(m) => m.file_type().is_symlink(),
Err(_) => false,
}
}

pub fn dir_exists(&self, path: &str) -> bool {
match fs::metadata(&self.plus(path)) {
Ok(m) => m.is_dir(),
Expand Down