From 61f114c9503b9462e9b2e9124ecb606e24cd74b6 Mon Sep 17 00:00:00 2001 From: Luigi Eliabe Date: Thu, 13 Jun 2024 17:57:12 +0000 Subject: [PATCH] cp: Fix broken symlinks to parent-dir According to the GNU cp official documentation[1], a symlink is only created if all source paths are absolute, unless the destination files are in the current directory. This fix solves this GNU cp incompatibility by: - Adding verifications when handling symlinks, to ensure that a symlink with relative path are only created if the destination is within the current directory. - Adding unit tests to cover this modifications, and ensure the behavior is correctly align with GNU documentation. [1]: https://www.gnu.org/software/coreutils/manual/html_node/cp-invocation.html#index-_002ds-16 --- src/uu/cp/src/cp.rs | 17 +++++++++++++++++ tests/by-util/test_cp.rs | 28 ++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 2b2a2a2bf09..e0d3716f1e6 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -7,6 +7,7 @@ use quick_error::quick_error; use std::cmp::Ordering; use std::collections::{HashMap, HashSet}; +use std::env; #[cfg(not(windows))] use std::ffi::CString; use std::fs::{self, File, Metadata, OpenOptions, Permissions}; @@ -1864,6 +1865,22 @@ fn handle_copy_mode( if dest.exists() && options.overwrite == OverwriteMode::Clobber(ClobberMode::Force) { fs::remove_file(dest)?; } + if source.is_relative() { + let current_dir = env::current_dir()?; + let abs_dest = + canonicalize(dest, MissingHandling::Missing, ResolveMode::Physical).unwrap(); + if let Some(parent) = abs_dest.parent() { + if parent != current_dir { + { + return Err(format!( + "{}: can make relative symbolic links only in current directory", + dest.maybe_quote() + ) + .into()); + } + } + } + } symlink_file(source, dest, symlinked_files)?; } CopyMode::Update => { diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 1e6586dc830..cb834780221 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -5573,3 +5573,31 @@ fn test_preserve_attrs_overriding_2() { assert_ne!(dest_file1_metadata.ino(), dest_file2_metadata.ino()); } } + +#[test] +fn test_symlink_to_subdir() { + let (at, mut ucmd) = at_and_ucmd!(); + + at.touch("file"); + at.mkdir("dir"); + + ucmd.args(&["--symbolic", "file", "dir"]) + .fails() + .no_stdout() + .stderr_is("cp: dir/file: can make relative symbolic links only in current directory\n"); + + assert!(at.dir_exists("dir")); + assert!(!at.file_exists("dir/file")); +} + +#[test] +fn test_symlink_from_subdir() { + let (at, mut ucmd) = at_and_ucmd!(); + + at.mkdir("dir"); + at.touch("dir/file"); + + ucmd.args(&["--symbolic", "dir/file", "."]).succeeds(); + + assert!(at.symlink_exists("file")); +}