Skip to content

Commit 2e16cbb

Browse files
cp: link-deref gnu test fix (#6378)
Co-authored-by: Ben Wiederhake <BenWiederhake.GitHub@gmx.de>
1 parent b718f95 commit 2e16cbb

File tree

3 files changed

+149
-6
lines changed

3 files changed

+149
-6
lines changed

src/uu/cp/src/copydir.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -337,10 +337,6 @@ pub(crate) fn copy_directory(
337337
copied_files: &mut HashMap<FileInformation, PathBuf>,
338338
source_in_command_line: bool,
339339
) -> CopyResult<()> {
340-
if !options.recursive {
341-
return Err(format!("-r not specified; omitting directory {}", root.quote()).into());
342-
}
343-
344340
// if no-dereference is enabled and this is a symlink, copy it as a file
345341
if !options.dereference(source_in_command_line) && root.is_symlink() {
346342
return copy_file(
@@ -355,6 +351,10 @@ pub(crate) fn copy_directory(
355351
);
356352
}
357353

354+
if !options.recursive {
355+
return Err(format!("-r not specified; omitting directory {}", root.quote()).into());
356+
}
357+
358358
// check if root is a prefix of target
359359
if path_has_prefix(target, root)? {
360360
return Err(format!(

src/uu/cp/src/cp.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -977,7 +977,9 @@ impl Options {
977977
dereference: !(matches.get_flag(options::NO_DEREFERENCE)
978978
|| matches.get_flag(options::NO_DEREFERENCE_PRESERVE_LINKS)
979979
|| matches.get_flag(options::ARCHIVE)
980-
|| recursive)
980+
// cp normally follows the link only when not copying recursively or when
981+
// --link (-l) is used
982+
|| (recursive && CopyMode::from_matches(matches)!= CopyMode::Link ))
981983
|| matches.get_flag(options::DEREFERENCE),
982984
one_file_system: matches.get_flag(options::ONE_FILE_SYSTEM),
983985
parents: matches.get_flag(options::PARENTS),
@@ -2063,7 +2065,13 @@ fn copy_file(
20632065
} else {
20642066
fs::symlink_metadata(source)
20652067
};
2066-
result.context(context)?
2068+
// this is just for gnu tests compatibility
2069+
result.map_err(|err| {
2070+
if err.to_string().contains("No such file or directory") {
2071+
return format!("cannot stat {}: No such file or directory", source.quote());
2072+
}
2073+
err.to_string()
2074+
})?
20672075
};
20682076

20692077
let dest_permissions = calculate_dest_permissions(dest, &source_metadata, options, context)?;

tests/by-util/test_cp.rs

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5307,3 +5307,138 @@ mod same_file {
53075307
assert_eq!(at.read(FILE_NAME), CONTENTS,);
53085308
}
53095309
}
5310+
5311+
// the following tests are for how the cp should behave when the source is a symlink
5312+
// and link option is given
5313+
#[cfg(all(unix, not(target_os = "android")))]
5314+
mod link_deref {
5315+
5316+
use crate::common::util::{AtPath, TestScenario};
5317+
use std::os::unix::fs::MetadataExt;
5318+
5319+
const FILE: &str = "file";
5320+
const FILE_LINK: &str = "file_link";
5321+
const DIR: &str = "dir";
5322+
const DIR_LINK: &str = "dir_link";
5323+
const DANG_LINK: &str = "dang_link";
5324+
const DST: &str = "dst";
5325+
5326+
fn setup_link_deref_tests(source: &str, at: &AtPath) {
5327+
match source {
5328+
FILE_LINK => {
5329+
at.touch(FILE);
5330+
at.symlink_file(FILE, FILE_LINK);
5331+
}
5332+
DIR_LINK => {
5333+
at.mkdir(DIR);
5334+
at.symlink_dir(DIR, DIR_LINK);
5335+
}
5336+
DANG_LINK => at.symlink_file("nowhere", DANG_LINK),
5337+
_ => {}
5338+
}
5339+
}
5340+
5341+
// cp --link shouldn't deref source if -P is given
5342+
#[test]
5343+
fn test_cp_symlink_as_source_with_link_and_no_deref() {
5344+
for src in [FILE_LINK, DIR_LINK, DANG_LINK] {
5345+
for r in [false, true] {
5346+
let scene = TestScenario::new(util_name!());
5347+
let at = &scene.fixtures;
5348+
setup_link_deref_tests(src, at);
5349+
let mut args = vec!["--link", "-P", src, DST];
5350+
if r {
5351+
args.push("-R");
5352+
};
5353+
scene.ucmd().args(&args).succeeds().no_stderr();
5354+
at.is_symlink(DST);
5355+
let src_ino = at.symlink_metadata(src).ino();
5356+
let dest_ino = at.symlink_metadata(DST).ino();
5357+
assert_eq!(src_ino, dest_ino);
5358+
}
5359+
}
5360+
}
5361+
5362+
// Dereferencing should fail for dangling symlink.
5363+
#[test]
5364+
fn test_cp_dang_link_as_source_with_link() {
5365+
for option in ["", "-L", "-H"] {
5366+
for r in [false, true] {
5367+
let scene = TestScenario::new(util_name!());
5368+
let at = &scene.fixtures;
5369+
setup_link_deref_tests(DANG_LINK, at);
5370+
let mut args = vec!["--link", DANG_LINK, DST];
5371+
if r {
5372+
args.push("-R");
5373+
};
5374+
if !option.is_empty() {
5375+
args.push(option);
5376+
}
5377+
scene
5378+
.ucmd()
5379+
.args(&args)
5380+
.fails()
5381+
.stderr_contains("No such file or directory");
5382+
}
5383+
}
5384+
}
5385+
5386+
// Dereferencing should fail for the 'dir_link' without -R.
5387+
#[test]
5388+
fn test_cp_dir_link_as_source_with_link() {
5389+
for option in ["", "-L", "-H"] {
5390+
let scene = TestScenario::new(util_name!());
5391+
let at = &scene.fixtures;
5392+
setup_link_deref_tests(DIR_LINK, at);
5393+
let mut args = vec!["--link", DIR_LINK, DST];
5394+
if !option.is_empty() {
5395+
args.push(option);
5396+
}
5397+
scene
5398+
.ucmd()
5399+
.args(&args)
5400+
.fails()
5401+
.stderr_contains("cp: -r not specified; omitting directory");
5402+
}
5403+
}
5404+
5405+
// cp --link -R 'dir_link' should create a new directory.
5406+
#[test]
5407+
fn test_cp_dir_link_as_source_with_link_and_r() {
5408+
for option in ["", "-L", "-H"] {
5409+
let scene = TestScenario::new(util_name!());
5410+
let at = &scene.fixtures;
5411+
setup_link_deref_tests(DIR_LINK, at);
5412+
let mut args = vec!["--link", "-R", DIR_LINK, DST];
5413+
if !option.is_empty() {
5414+
args.push(option);
5415+
}
5416+
scene.ucmd().args(&args).succeeds();
5417+
at.dir_exists(DST);
5418+
}
5419+
}
5420+
5421+
//cp --link 'file_link' should create a hard link to the target.
5422+
#[test]
5423+
fn test_cp_file_link_as_source_with_link() {
5424+
for option in ["", "-L", "-H"] {
5425+
for r in [false, true] {
5426+
let scene = TestScenario::new(util_name!());
5427+
let at = &scene.fixtures;
5428+
setup_link_deref_tests(FILE_LINK, at);
5429+
let mut args = vec!["--link", "-R", FILE_LINK, DST];
5430+
if !option.is_empty() {
5431+
args.push(option);
5432+
}
5433+
if r {
5434+
args.push("-R");
5435+
}
5436+
scene.ucmd().args(&args).succeeds();
5437+
at.file_exists(DST);
5438+
let src_ino = at.symlink_metadata(FILE).ino();
5439+
let dest_ino = at.symlink_metadata(DST).ino();
5440+
assert_eq!(src_ino, dest_ino);
5441+
}
5442+
}
5443+
}
5444+
}

0 commit comments

Comments
 (0)