Skip to content

Commit 09d870f

Browse files
committed
cp: link-deref gnu test fix
1 parent 421f3a9 commit 09d870f

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
@@ -332,10 +332,6 @@ pub(crate) fn copy_directory(
332332
copied_files: &mut HashMap<FileInformation, PathBuf>,
333333
source_in_command_line: bool,
334334
) -> CopyResult<()> {
335-
if !options.recursive {
336-
return Err(format!("-r not specified; omitting directory {}", root.quote()).into());
337-
}
338-
339335
// if no-dereference is enabled and this is a symlink, copy it as a file
340336
if !options.dereference(source_in_command_line) && root.is_symlink() {
341337
return copy_file(
@@ -349,6 +345,10 @@ pub(crate) fn copy_directory(
349345
);
350346
}
351347

348+
if !options.recursive {
349+
return Err(format!("-r not specified; omitting directory {}", root.quote()).into());
350+
}
351+
352352
// check if root is a prefix of target
353353
if path_has_prefix(target, root)? {
354354
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),
@@ -2046,7 +2048,13 @@ fn copy_file(
20462048
} else {
20472049
fs::symlink_metadata(source)
20482050
};
2049-
result.context(context)?
2051+
// this is just for gnu tests compatibility
2052+
result.map_err(|err| {
2053+
if err.to_string().contains("No such file or directory") {
2054+
return format!("cannot stat {}: No such file or directory", source.quote());
2055+
}
2056+
err.to_string()
2057+
})?
20502058
};
20512059

20522060
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
@@ -5245,3 +5245,138 @@ mod same_file {
52455245
assert_eq!(at.read(FILE_NAME), CONTENTS,);
52465246
}
52475247
}
5248+
5249+
// the following tests are for how the cp should behave when the source is a symlink
5250+
// and link option is given
5251+
#[cfg(unix)]
5252+
mod link_deref {
5253+
5254+
use crate::common::util::{AtPath, TestScenario};
5255+
use std::os::unix::fs::MetadataExt;
5256+
5257+
const FILE: &str = "file";
5258+
const FILE_LINK: &str = "file_link";
5259+
const DIR: &str = "dir";
5260+
const DIR_LINK: &str = "dir_link";
5261+
const DANG_LINK: &str = "dang_link";
5262+
const DST: &str = "dst";
5263+
5264+
fn setup_link_deref_tests(source: &str, at: &AtPath) {
5265+
match source {
5266+
FILE_LINK => {
5267+
at.touch(FILE);
5268+
at.symlink_file(FILE, FILE_LINK)
5269+
}
5270+
DIR_LINK => {
5271+
at.mkdir(DIR);
5272+
at.symlink_dir(DIR, DIR_LINK)
5273+
}
5274+
DANG_LINK => at.symlink_file("nowhere", DANG_LINK),
5275+
_ => todo!(),
5276+
}
5277+
}
5278+
5279+
// cp --link shouldn't deref source if -P is given
5280+
#[test]
5281+
fn test_cp_symlink_as_source_with_link_and_no_deref() {
5282+
for src in [FILE_LINK, DIR_LINK, DANG_LINK] {
5283+
for r in [false, true] {
5284+
let scene = TestScenario::new(util_name!());
5285+
let at = &scene.fixtures;
5286+
setup_link_deref_tests(src, at);
5287+
let mut args = vec!["--link", "-P", src, DST];
5288+
if r {
5289+
args.push("-R")
5290+
};
5291+
scene.ucmd().args(&args).succeeds().no_stderr();
5292+
at.is_symlink(DST);
5293+
let src_ino = at.symlink_metadata(src).ino();
5294+
let dest_ino = at.symlink_metadata(DST).ino();
5295+
assert_eq!(src_ino, dest_ino,);
5296+
}
5297+
}
5298+
}
5299+
5300+
// Dereferencing should fail for dangling symlink.
5301+
#[test]
5302+
fn test_cp_dang_link_as_source_with_link() {
5303+
for option in ["", "-L", "-H"] {
5304+
for r in [false, true] {
5305+
let scene = TestScenario::new(util_name!());
5306+
let at = &scene.fixtures;
5307+
setup_link_deref_tests(DANG_LINK, at);
5308+
let mut args = vec!["--link", DANG_LINK, DST];
5309+
if r {
5310+
args.push("-R")
5311+
};
5312+
if !option.is_empty() {
5313+
args.push(&option)
5314+
}
5315+
scene
5316+
.ucmd()
5317+
.args(&args)
5318+
.fails()
5319+
.stderr_contains("No such file or directory");
5320+
}
5321+
}
5322+
}
5323+
5324+
// Dereferencing should fail for the 'dir_link' without -R.
5325+
#[test]
5326+
fn test_cp_dir_link_as_source_with_link() {
5327+
for option in ["", "-L", "-H"] {
5328+
let scene = TestScenario::new(util_name!());
5329+
let at = &scene.fixtures;
5330+
setup_link_deref_tests(DIR_LINK, at);
5331+
let mut args = vec!["--link", DIR_LINK, DST];
5332+
if !option.is_empty() {
5333+
args.push(&option)
5334+
}
5335+
scene
5336+
.ucmd()
5337+
.args(&args)
5338+
.fails()
5339+
.stderr_contains("cp: -r not specified; omitting directory");
5340+
}
5341+
}
5342+
5343+
// cp --link -R 'dir_link' should create a new directory.
5344+
#[test]
5345+
fn test_cp_dir_link_as_source_with_link_and_r() {
5346+
for option in ["", "-L", "-H"] {
5347+
let scene = TestScenario::new(util_name!());
5348+
let at = &scene.fixtures;
5349+
setup_link_deref_tests(DIR_LINK, at);
5350+
let mut args = vec!["--link", "-R", DIR_LINK, DST];
5351+
if !option.is_empty() {
5352+
args.push(&option)
5353+
}
5354+
scene.ucmd().args(&args).succeeds();
5355+
at.dir_exists(DST);
5356+
}
5357+
}
5358+
5359+
//cp --link 'file_link' should create a hard link to the target.
5360+
#[test]
5361+
fn test_cp_file_link_as_source_with_link() {
5362+
for option in ["", "-L", "-H"] {
5363+
for r in [false, true] {
5364+
let scene = TestScenario::new(util_name!());
5365+
let at = &scene.fixtures;
5366+
setup_link_deref_tests(FILE_LINK, at);
5367+
let mut args = vec!["--link", "-R", FILE_LINK, DST];
5368+
if !option.is_empty() {
5369+
args.push(&option)
5370+
}
5371+
if r {
5372+
args.push("-R")
5373+
}
5374+
scene.ucmd().args(&args).succeeds();
5375+
at.file_exists(DST);
5376+
let src_ino = at.symlink_metadata(FILE).ino();
5377+
let dest_ino = at.symlink_metadata(DST).ino();
5378+
assert_eq!(src_ino,dest_ino)
5379+
}
5380+
}
5381+
}
5382+
}

0 commit comments

Comments
 (0)