1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 11:37:44 +00:00

Added overwrite detection for existing symlinks

This commit is contained in:
Anirban Halder 2024-05-07 23:59:49 +05:30 committed by Ben Wiederhake
parent 8078fca99b
commit f7e55b1322
3 changed files with 58 additions and 0 deletions

View file

@ -221,6 +221,7 @@ fn copy_direntry(
options: &Options, options: &Options,
symlinked_files: &mut HashSet<FileInformation>, symlinked_files: &mut HashSet<FileInformation>,
preserve_hard_links: bool, preserve_hard_links: bool,
copied_destinations: &HashSet<PathBuf>,
copied_files: &mut HashMap<FileInformation, PathBuf>, copied_files: &mut HashMap<FileInformation, PathBuf>,
) -> CopyResult<()> { ) -> CopyResult<()> {
let Entry { let Entry {
@ -267,6 +268,7 @@ fn copy_direntry(
local_to_target.as_path(), local_to_target.as_path(),
options, options,
symlinked_files, symlinked_files,
copied_destinations,
copied_files, copied_files,
false, false,
) { ) {
@ -295,6 +297,7 @@ fn copy_direntry(
local_to_target.as_path(), local_to_target.as_path(),
options, options,
symlinked_files, symlinked_files,
copied_destinations,
copied_files, copied_files,
false, false,
) { ) {
@ -329,6 +332,7 @@ pub(crate) fn copy_directory(
target: &Path, target: &Path,
options: &Options, options: &Options,
symlinked_files: &mut HashSet<FileInformation>, symlinked_files: &mut HashSet<FileInformation>,
copied_destinations: &HashSet<PathBuf>,
copied_files: &mut HashMap<FileInformation, PathBuf>, copied_files: &mut HashMap<FileInformation, PathBuf>,
source_in_command_line: bool, source_in_command_line: bool,
) -> CopyResult<()> { ) -> CopyResult<()> {
@ -344,6 +348,7 @@ pub(crate) fn copy_directory(
target, target,
options, options,
symlinked_files, symlinked_files,
copied_destinations,
copied_files, copied_files,
source_in_command_line, source_in_command_line,
); );
@ -417,6 +422,7 @@ pub(crate) fn copy_directory(
options, options,
symlinked_files, symlinked_files,
preserve_hard_links, preserve_hard_links,
copied_destinations,
copied_files, copied_files,
)?; )?;
} }

View file

@ -1220,6 +1220,7 @@ pub fn copy(sources: &[PathBuf], target: &Path, options: &Options) -> CopyResult
target_type, target_type,
options, options,
&mut symlinked_files, &mut symlinked_files,
&copied_destinations,
&mut copied_files, &mut copied_files,
) { ) {
show_error_if_needed(&error); show_error_if_needed(&error);
@ -1279,6 +1280,7 @@ fn copy_source(
target_type: TargetType, target_type: TargetType,
options: &Options, options: &Options,
symlinked_files: &mut HashSet<FileInformation>, symlinked_files: &mut HashSet<FileInformation>,
copied_destinations: &HashSet<PathBuf>,
copied_files: &mut HashMap<FileInformation, PathBuf>, copied_files: &mut HashMap<FileInformation, PathBuf>,
) -> CopyResult<()> { ) -> CopyResult<()> {
let source_path = Path::new(&source); let source_path = Path::new(&source);
@ -1290,6 +1292,7 @@ fn copy_source(
target, target,
options, options,
symlinked_files, symlinked_files,
copied_destinations,
copied_files, copied_files,
true, true,
) )
@ -1302,6 +1305,7 @@ fn copy_source(
dest.as_path(), dest.as_path(),
options, options,
symlinked_files, symlinked_files,
copied_destinations,
copied_files, copied_files,
true, true,
); );
@ -1917,6 +1921,7 @@ fn copy_file(
dest: &Path, dest: &Path,
options: &Options, options: &Options,
symlinked_files: &mut HashSet<FileInformation>, symlinked_files: &mut HashSet<FileInformation>,
copied_destinations: &HashSet<PathBuf>,
copied_files: &mut HashMap<FileInformation, PathBuf>, copied_files: &mut HashMap<FileInformation, PathBuf>,
source_in_command_line: bool, source_in_command_line: bool,
) -> CopyResult<()> { ) -> CopyResult<()> {
@ -1934,6 +1939,17 @@ fn copy_file(
dest.display() dest.display()
))); )));
} }
// Fail if cp tries to copy two sources of the same name into a single symlink
// Example: "cp file1 dir1/file1 tmp" where "tmp" is a directory containing a symlink "file1" pointing to a file named "foo".
// foo will contain the contents of "file1" and "dir1/file1" will not be copied over to "tmp/file1"
if copied_destinations.contains(dest) {
return Err(Error::Error(format!(
"will not copy '{}' through just-created symlink '{}'",
source.display(),
dest.display()
)));
}
let copy_contents = options.dereference(source_in_command_line) || !source_is_symlink; let copy_contents = options.dereference(source_in_command_line) || !source_is_symlink;
if copy_contents if copy_contents
&& !dest.exists() && !dest.exists()

View file

@ -2660,6 +2660,42 @@ fn test_copy_through_dangling_symlink_no_dereference() {
.no_stdout(); .no_stdout();
} }
#[test]
fn test_cp_symlink_overwrite_detection() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.mkdir("good");
at.mkdir("tmp");
at.touch("README");
at.touch("good/README");
at.write("README", "file1");
at.write("good/README", "file2");
ts.ccmd("ln")
.arg("-s")
.arg("foo")
.arg("tmp/README")
.succeeds();
at.touch("tmp/foo");
let result = ts
.ucmd()
.arg("README")
.arg("good/README")
.arg("tmp")
.fails();
let stderr = result.stderr_str();
assert_eq!(
"cp: will not copy 'good/README' through just-created symlink 'tmp/README'\n",
stderr
);
let contents = at.read("tmp/foo");
assert_eq!(contents, "file1");
}
/// Test for copying a dangling symbolic link and its permissions. /// Test for copying a dangling symbolic link and its permissions.
#[cfg(not(target_os = "freebsd"))] // FIXME: fix this test for FreeBSD #[cfg(not(target_os = "freebsd"))] // FIXME: fix this test for FreeBSD
#[test] #[test]