diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 1ad07a53d..fe710621d 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -418,6 +418,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .conflicts_with(OPT_DEREFERENCE) // -d sets this option .help("never follow symbolic links in SOURCE")) + .arg(Arg::with_name(OPT_DEREFERENCE) + .short("L") + .long(OPT_DEREFERENCE) + .conflicts_with(OPT_NO_DEREFERENCE) + .help("always follow symbolic links in SOURCE")) .arg(Arg::with_name(OPT_ARCHIVE) .short("a") .long(OPT_ARCHIVE) @@ -432,11 +437,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .long(OPT_COPY_CONTENTS) .conflicts_with(OPT_ATTRIBUTES_ONLY) .help("NotImplemented: copy contents of special files when recursive")) - .arg(Arg::with_name(OPT_DEREFERENCE) - .short("L") - .long(OPT_DEREFERENCE) - .conflicts_with(OPT_NO_DEREFERENCE) - .help("NotImplemented: always follow symbolic links in SOURCE")) .arg(Arg::with_name(OPT_PARENTS) .long(OPT_PARENTS) .help("NotImplemented: use full source file name under DIRECTORY")) @@ -546,7 +546,7 @@ impl FromStr for Attribute { return Err(Error::InvalidArgument(format!( "invalid attribute '{}'", value - ))) + ))); } }) } @@ -568,7 +568,6 @@ impl Options { fn from_matches(matches: &ArgMatches) -> CopyResult { let not_implemented_opts = vec![ OPT_COPY_CONTENTS, - OPT_DEREFERENCE, OPT_PARENTS, OPT_SPARSE, OPT_STRIP_TRAILING_SLASHES, @@ -649,7 +648,7 @@ impl Options { return Err(Error::InvalidArgument(format!( "invalid argument '{}' for \'reflink\'", value - ))) + ))); } } } else { @@ -927,7 +926,7 @@ fn copy_directory(root: &Path, target: &Target, options: &Options) -> CopyResult for path in WalkDir::new(root) { let p = or_continue!(path); let is_symlink = fs::symlink_metadata(p.path())?.file_type().is_symlink(); - let path = if options.no_dereference && is_symlink { + let path = if (options.no_dereference || options.dereference) && is_symlink { // we are dealing with a symlink. Don't follow it match env::current_dir() { Ok(cwd) => cwd.join(resolve_relative_path(p.path())), diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 8c0900ba2..a18386b97 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -351,6 +351,60 @@ fn test_cp_arg_suffix() { ); } +#[test] +fn test_cp_deref_conflicting_options() { + let (_at, mut ucmd) = at_and_ucmd!(); + + ucmd.arg("-LP") + .arg(TEST_COPY_TO_FOLDER) + .arg(TEST_HELLO_WORLD_SOURCE) + .fails(); +} + +#[test] +fn test_cp_deref() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + #[cfg(not(windows))] + let _r = fs::symlink( + TEST_HELLO_WORLD_SOURCE, + at.subdir.join(TEST_HELLO_WORLD_SOURCE_SYMLINK), + ); + #[cfg(windows)] + let _r = symlink_file( + TEST_HELLO_WORLD_SOURCE, + at.subdir.join(TEST_HELLO_WORLD_SOURCE_SYMLINK), + ); + //using -L option + let result = scene + .ucmd() + .arg("-L") + .arg(TEST_HELLO_WORLD_SOURCE) + .arg(TEST_HELLO_WORLD_SOURCE_SYMLINK) + .arg(TEST_COPY_TO_FOLDER) + .run(); + + // Check that the exit code represents a successful copy. + let exit_success = result.success; + assert!(exit_success); + let path_to_new_symlink = at + .subdir + .join(TEST_COPY_TO_FOLDER) + .join(TEST_HELLO_WORLD_SOURCE_SYMLINK); + // unlike -P/--no-deref, we expect a file, not a link + assert!(at.file_exists( + &path_to_new_symlink + .clone() + .into_os_string() + .into_string() + .unwrap() + )); + // Check the content of the destination file that was copied. + assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n"); + let path_to_check = path_to_new_symlink.to_str().unwrap(); + assert_eq!(at.read(&path_to_check), "Hello, World!\n"); +} #[test] fn test_cp_no_deref() { let scene = TestScenario::new(util_name!()); @@ -395,6 +449,111 @@ fn test_cp_no_deref() { assert_eq!(at.read(&path_to_check), "Hello, World!\n"); } +#[test] +// For now, disable the test on Windows. Symlinks aren't well support on Windows. +// It works on Unix for now and it works locally when run from a powershell +#[cfg(not(windows))] +fn test_cp_deref_folder_to_folder() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let cwd = env::current_dir().unwrap(); + + let path_to_new_symlink = at.subdir.join(TEST_COPY_FROM_FOLDER); + + // Change the cwd to have a correct symlink + assert!(env::set_current_dir(&path_to_new_symlink).is_ok()); + + #[cfg(not(windows))] + let _r = fs::symlink(TEST_HELLO_WORLD_SOURCE, TEST_HELLO_WORLD_SOURCE_SYMLINK); + #[cfg(windows)] + let _r = symlink_file(TEST_HELLO_WORLD_SOURCE, TEST_HELLO_WORLD_SOURCE_SYMLINK); + + // Back to the initial cwd (breaks the other tests) + assert!(env::set_current_dir(&cwd).is_ok()); + + //using -P -R option + let result = scene + .ucmd() + .arg("-L") + .arg("-R") + .arg("-v") + .arg(TEST_COPY_FROM_FOLDER) + .arg(TEST_COPY_TO_FOLDER_NEW) + .run(); + println!("cp output {}", result.stdout); + + // Check that the exit code represents a successful copy. + let exit_success = result.success; + assert!(exit_success); + + #[cfg(not(windows))] + { + let scene2 = TestScenario::new("ls"); + let result = scene2.cmd("ls").arg("-al").arg(path_to_new_symlink).run(); + println!("ls source {}", result.stdout); + + let path_to_new_symlink = at.subdir.join(TEST_COPY_TO_FOLDER_NEW); + + let result = scene2.cmd("ls").arg("-al").arg(path_to_new_symlink).run(); + println!("ls dest {}", result.stdout); + } + + #[cfg(windows)] + { + // No action as this test is disabled but kept in case we want to + // try to make it work in the future. + let a = Command::new("cmd").args(&["/C", "dir"]).output(); + println!("output {:#?}", a); + + let a = Command::new("cmd") + .args(&["/C", "dir", &at.as_string()]) + .output(); + println!("output {:#?}", a); + + let a = Command::new("cmd") + .args(&["/C", "dir", path_to_new_symlink.to_str().unwrap()]) + .output(); + println!("output {:#?}", a); + + let path_to_new_symlink = at.subdir.join(TEST_COPY_FROM_FOLDER); + + let a = Command::new("cmd") + .args(&["/C", "dir", path_to_new_symlink.to_str().unwrap()]) + .output(); + println!("output {:#?}", a); + + let path_to_new_symlink = at.subdir.join(TEST_COPY_TO_FOLDER_NEW); + + let a = Command::new("cmd") + .args(&["/C", "dir", path_to_new_symlink.to_str().unwrap()]) + .output(); + println!("output {:#?}", a); + } + + let path_to_new_symlink = at + .subdir + .join(TEST_COPY_TO_FOLDER_NEW) + .join(TEST_HELLO_WORLD_SOURCE_SYMLINK); + assert!(at.file_exists( + &path_to_new_symlink + .clone() + .into_os_string() + .into_string() + .unwrap() + )); + + let path_to_new = at.subdir.join(TEST_COPY_TO_FOLDER_NEW_FILE); + + // Check the content of the destination file that was copied. + let path_to_check = path_to_new.to_str().unwrap(); + assert_eq!(at.read(path_to_check), "Hello, World!\n"); + + // Check the content of the symlink + let path_to_check = path_to_new_symlink.to_str().unwrap(); + assert_eq!(at.read(&path_to_check), "Hello, World!\n"); +} + #[test] // For now, disable the test on Windows. Symlinks aren't well support on Windows. // It works on Unix for now and it works locally when run from a powershell