diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 0a307ee9e..cb3275ff9 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -29,8 +29,9 @@ use platform::copy_on_write; use uucore::display::Quotable; use uucore::error::{set_exit_code, UClapError, UError, UResult, UUsageError}; use uucore::fs::{ - are_hardlinks_to_same_file, canonicalize, is_symlink_loop, path_ends_with_terminator, - paths_refer_to_same_file, FileInformation, MissingHandling, ResolveMode, + are_hardlinks_to_same_file, canonicalize, get_filename, is_symlink_loop, + path_ends_with_terminator, paths_refer_to_same_file, FileInformation, MissingHandling, + ResolveMode, }; use uucore::{backup_control, update_control}; // These are exposed for projects (e.g. nushell) that want to create an `Options` value, which @@ -1468,16 +1469,23 @@ pub(crate) fn copy_attributes( fn symlink_file( source: &Path, dest: &Path, - context: &str, symlinked_files: &mut HashSet, ) -> CopyResult<()> { #[cfg(not(windows))] { - std::os::unix::fs::symlink(source, dest).context(context)?; + std::os::unix::fs::symlink(source, dest).context(format!( + "cannot create symlink {} to {}", + get_filename(dest).unwrap_or("invalid file name").quote(), + get_filename(source).unwrap_or("invalid file name").quote() + ))?; } #[cfg(windows)] { - std::os::windows::fs::symlink_file(source, dest).context(context)?; + std::os::windows::fs::symlink_file(source, dest).context(format!( + "cannot create symlink {} to {}", + get_filename(dest).unwrap_or("invalid file name").quote(), + get_filename(source).unwrap_or("invalid file name").quote() + ))?; } if let Ok(file_info) = FileInformation::from_path(dest, false) { symlinked_files.insert(file_info); @@ -1489,10 +1497,11 @@ fn context_for(src: &Path, dest: &Path) -> String { format!("{} -> {}", src.quote(), dest.quote()) } -/// Implements a simple backup copy for the destination file. +/// Implements a simple backup copy for the destination file . +/// if is_dest_symlink flag is set to true dest will be renamed to backup_path /// TODO: for the backup, should this function be replaced by `copy_file(...)`? -fn backup_dest(dest: &Path, backup_path: &Path) -> CopyResult { - if dest.is_symlink() { +fn backup_dest(dest: &Path, backup_path: &Path, is_dest_symlink: bool) -> CopyResult { + if is_dest_symlink { fs::rename(dest, backup_path)?; } else { fs::copy(dest, backup_path)?; @@ -1513,11 +1522,38 @@ fn is_forbidden_to_copy_to_same_file( ) -> bool { // TODO To match the behavior of GNU cp, we also need to check // that the file is a regular file. + let source_is_symlink = source.is_symlink(); + let dest_is_symlink = dest.is_symlink(); + // only disable dereference if both source and dest is symlink and dereference flag is disabled let dereference_to_compare = - options.dereference(source_in_command_line) || !source.is_symlink(); - paths_refer_to_same_file(source, dest, dereference_to_compare) - && !(options.force() && options.backup != BackupMode::NoBackup) - && !(dest.is_symlink() && options.backup != BackupMode::NoBackup) + options.dereference(source_in_command_line) || (!source_is_symlink || !dest_is_symlink); + if !paths_refer_to_same_file(source, dest, dereference_to_compare) { + return false; + } + if options.backup != BackupMode::NoBackup { + if options.force() && !source_is_symlink { + return false; + } + if source_is_symlink && !options.dereference { + return false; + } + if dest_is_symlink { + return false; + } + if !dest_is_symlink && !source_is_symlink && dest != source { + return false; + } + } + if options.copy_mode == CopyMode::Link { + return false; + } + if options.copy_mode == CopyMode::SymLink && dest_is_symlink { + return false; + } + if dest_is_symlink && source_is_symlink && !options.dereference { + return false; + } + true } /// Back up, remove, or leave intact the destination file, depending on the options. @@ -1526,6 +1562,7 @@ fn handle_existing_dest( dest: &Path, options: &Options, source_in_command_line: bool, + copied_files: &mut HashMap, ) -> CopyResult<()> { // Disallow copying a file to itself, unless `--force` and // `--backup` are both specified. @@ -1537,6 +1574,7 @@ fn handle_existing_dest( options.overwrite.verify(dest)?; } + let mut is_dest_removed = false; let backup_path = backup_control::get_backup_path(options.backup, dest, &options.backup_suffix); if let Some(backup_path) = backup_path { if paths_refer_to_same_file(source, &backup_path, true) { @@ -1547,13 +1585,16 @@ fn handle_existing_dest( ) .into()); } else { - backup_dest(dest, &backup_path)?; + is_dest_removed = dest.is_symlink(); + backup_dest(dest, &backup_path, is_dest_removed)?; } } match options.overwrite { // FIXME: print that the file was removed if --verbose is enabled OverwriteMode::Clobber(ClobberMode::Force) => { - if is_symlink_loop(dest) || fs::metadata(dest)?.permissions().readonly() { + if !is_dest_removed + && (is_symlink_loop(dest) || fs::metadata(dest)?.permissions().readonly()) + { fs::remove_file(dest)?; } } @@ -1574,7 +1615,19 @@ fn handle_existing_dest( // `dest/src/f` and `dest/src/f` has the contents of // `src/f`, we delete the existing file to allow the hard // linking. - if options.preserve_hard_links() { + + if options.preserve_hard_links() + // only try to remove dest file only if the current source + // is hardlink to a file that is already copied + && copied_files.contains_key( + &FileInformation::from_path( + source, + options.dereference(source_in_command_line), + ) + .context(format!("cannot stat {}", source.quote()))?, + ) + && !is_dest_removed + { fs::remove_file(dest)?; } } @@ -1700,7 +1753,7 @@ fn handle_copy_mode( let backup_path = backup_control::get_backup_path(options.backup, dest, &options.backup_suffix); if let Some(backup_path) = backup_path { - backup_dest(dest, &backup_path)?; + backup_dest(dest, &backup_path, dest.is_symlink())?; fs::remove_file(dest)?; } if options.overwrite == OverwriteMode::Clobber(ClobberMode::Force) { @@ -1714,7 +1767,11 @@ fn handle_copy_mode( } else { fs::hard_link(source, dest) } - .context(context)?; + .context(format!( + "cannot create hard link {} to {}", + get_filename(dest).unwrap_or("invalid file name").quote(), + get_filename(source).unwrap_or("invalid file name").quote() + ))?; } CopyMode::Copy => { copy_helper( @@ -1731,7 +1788,7 @@ fn handle_copy_mode( if dest.exists() && options.overwrite == OverwriteMode::Clobber(ClobberMode::Force) { fs::remove_file(dest)?; } - symlink_file(source, dest, context, symlinked_files)?; + symlink_file(source, dest, symlinked_files)?; } CopyMode::Update => { if dest.exists() { @@ -1860,8 +1917,10 @@ fn copy_file( copied_files: &mut HashMap, source_in_command_line: bool, ) -> CopyResult<()> { + let source_is_symlink = source.is_symlink(); + let dest_is_symlink = dest.is_symlink(); // Fail if dest is a dangling symlink or a symlink this program created previously - if dest.is_symlink() { + if dest_is_symlink { if FileInformation::from_path(dest, false) .map(|info| symlinked_files.contains(&info)) .unwrap_or(false) @@ -1872,7 +1931,7 @@ fn copy_file( 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 && !dest.exists() && !matches!( @@ -1898,6 +1957,7 @@ fn copy_file( } if are_hardlinks_to_same_file(source, dest) + && source != dest && matches!( options.overwrite, OverwriteMode::Clobber(ClobberMode::RemoveDestination) @@ -1913,19 +1973,37 @@ fn copy_file( OverwriteMode::Clobber(ClobberMode::RemoveDestination) )) { - if are_hardlinks_to_same_file(source, dest) - && !options.force() - && options.backup == BackupMode::NoBackup - && source != dest - || (source == dest && options.copy_mode == CopyMode::Link) - { - return Ok(()); + if paths_refer_to_same_file(source, dest, true) && options.copy_mode == CopyMode::Link { + if source_is_symlink { + if !dest_is_symlink { + return Ok(()); + } + if !options.dereference { + return Ok(()); + } + } else if options.backup != BackupMode::NoBackup && !dest_is_symlink { + if source == dest { + if !options.force() { + return Ok(()); + } + } else { + return Ok(()); + } + } + } + handle_existing_dest(source, dest, options, source_in_command_line, copied_files)?; + if are_hardlinks_to_same_file(source, dest) { + if options.copy_mode == CopyMode::Copy && options.backup != BackupMode::NoBackup { + return Ok(()); + } + if options.copy_mode == CopyMode::Link && (!source_is_symlink || !dest_is_symlink) { + return Ok(()); + } } - handle_existing_dest(source, dest, options, source_in_command_line)?; } if options.attributes_only - && source.is_symlink() + && source_is_symlink && !matches!( options.overwrite, OverwriteMode::Clobber(ClobberMode::RemoveDestination) @@ -1981,7 +2059,7 @@ fn copy_file( )?; // TODO: implement something similar to gnu's lchown - if !dest.is_symlink() { + if !dest_is_symlink { // Here, to match GNU semantics, we quietly ignore an error // if a user does not have the correct ownership to modify // the permissions of a file. @@ -2130,7 +2208,7 @@ fn copy_link( if dest.is_symlink() || dest.is_file() { fs::remove_file(dest)?; } - symlink_file(&link, dest, &context_for(&link, dest), symlinked_files) + symlink_file(&link, dest, symlinked_files) } /// Generate an error message if `target` is not the correct `target_type` diff --git a/src/uucore/src/lib/features/backup_control.rs b/src/uucore/src/lib/features/backup_control.rs index 99889a6ff..9086acb19 100644 --- a/src/uucore/src/lib/features/backup_control.rs +++ b/src/uucore/src/lib/features/backup_control.rs @@ -354,8 +354,13 @@ pub fn determine_backup_mode(matches: &ArgMatches) -> UResult { } } else if matches.get_flag(arguments::OPT_BACKUP_NO_ARG) { // the short form of this option, -b does not accept any argument. - // Using -b is equivalent to using --backup=existing. - Ok(BackupMode::ExistingBackup) + // if VERSION_CONTROL is not set then using -b is equivalent to + // using --backup=existing. + if let Ok(method) = env::var("VERSION_CONTROL") { + match_method(&method, "$VERSION_CONTROL") + } else { + Ok(BackupMode::ExistingBackup) + } } else { // No option was present at all Ok(BackupMode::NoBackup) @@ -578,16 +583,16 @@ mod tests { assert_eq!(result, BackupMode::SimpleBackup); } - // -b ignores the "VERSION_CONTROL" environment variable + // -b doesn't ignores the "VERSION_CONTROL" environment variable #[test] - fn test_backup_mode_short_only_ignore_env() { + fn test_backup_mode_short_does_not_ignore_env() { let _dummy = TEST_MUTEX.lock().unwrap(); - env::set_var(ENV_VERSION_CONTROL, "none"); + env::set_var(ENV_VERSION_CONTROL, "numbered"); let matches = make_app().get_matches_from(vec!["command", "-b"]); let result = determine_backup_mode(&matches).unwrap(); - assert_eq!(result, BackupMode::ExistingBackup); + assert_eq!(result, BackupMode::NumberedBackup); env::remove_var(ENV_VERSION_CONTROL); } diff --git a/src/uucore/src/lib/features/fs.rs b/src/uucore/src/lib/features/fs.rs index f43c18ebd..73c61e0a3 100644 --- a/src/uucore/src/lib/features/fs.rs +++ b/src/uucore/src/lib/features/fs.rs @@ -770,6 +770,25 @@ pub mod sane_blksize { } } +/// Extracts the filename component from the given `file` path and returns it as an `Option<&str>`. +/// +/// If the `file` path contains a filename, this function returns `Some(filename)` where `filename` is +/// the extracted filename as a string slice (`&str`). If the `file` path does not have a filename +/// component or if the filename is not valid UTF-8, it returns `None`. +/// +/// # Arguments +/// +/// * `file`: A reference to a `Path` representing the file path from which to extract the filename. +/// +/// # Returns +/// +/// * `Some(filename)`: If a valid filename exists in the `file` path, where `filename` is the +/// extracted filename as a string slice (`&str`). +/// * `None`: If the `file` path does not contain a valid filename or if the filename is not valid UTF-8. +pub fn get_filename(file: &Path) -> Option<&str> { + file.file_name().and_then(|filename| filename.to_str()) +} + #[cfg(test)] mod tests { // Note this useful idiom: importing names from outer (for mod tests) scope. @@ -1006,4 +1025,9 @@ mod tests { assert_eq!(0x2000_0000, sane_blksize::sane_blksize(0x2000_0000)); assert_eq!(512, sane_blksize::sane_blksize(0x2000_0001)); } + #[test] + fn test_get_file_name() { + let file_path = PathBuf::from("~/foo.txt"); + assert!(matches!(get_filename(&file_path), Some("foo.txt"))); + } } diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 1f81011db..e417e40b1 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -3,7 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. // spell-checker:ignore (flags) reflink (fs) tmpfs (linux) rlimit Rlim NOFILE clob btrfs neve ROOTDIR USERDIR procfs outfile uufs xattrs - +// spell-checker:ignore bdfl hlsl use crate::common::util::TestScenario; #[cfg(not(windows))] use std::fs::set_permissions; @@ -3886,3 +3886,927 @@ fn test_cp_no_dereference_attributes_only_with_symlink() { "file2 content does not match expected" ); } +#[cfg(all(unix, not(target_os = "android")))] +#[cfg(test)] +/// contains the test for cp when the source and destination points to the same file +mod same_file { + + use crate::common::util::TestScenario; + + const FILE_NAME: &str = "foo"; + const SYMLINK_NAME: &str = "symlink"; + const CONTENTS: &str = "abcd"; + + // the following tests tries to copy a file to the symlink of the same file with + // various options + #[test] + fn test_same_file_from_file_to_symlink() { + for option in ["-d", "-f", "-df"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + scene + .ucmd() + .args(&[option, FILE_NAME, SYMLINK_NAME]) + .fails() + .stderr_contains("'foo' and 'symlink' are the same file"); + assert!(at.symlink_exists(SYMLINK_NAME)); + assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME)); + assert!(at.file_exists(FILE_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + } + + #[test] + fn test_same_file_from_file_to_symlink_with_rem_option() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + scene + .ucmd() + .args(&["--rem", FILE_NAME, SYMLINK_NAME]) + .succeeds(); + assert!(at.file_exists(SYMLINK_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + assert!(at.file_exists(FILE_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + + #[test] + fn test_same_file_from_file_to_symlink_with_backup_option() { + for option in ["-b", "-bd", "-bf", "-bdf"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let backup = "symlink~"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + scene + .ucmd() + .args(&[option, FILE_NAME, SYMLINK_NAME]) + .succeeds(); + assert!(at.symlink_exists(backup)); + assert_eq!(FILE_NAME, at.resolve_link(backup)); + assert!(at.file_exists(SYMLINK_NAME)); + assert_eq!(at.read(SYMLINK_NAME), CONTENTS,); + assert!(at.file_exists(FILE_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + } + + #[test] + fn test_same_file_from_file_to_symlink_with_link_option() { + for option in ["-l", "-dl"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + scene + .ucmd() + .args(&[option, FILE_NAME, SYMLINK_NAME]) + .fails() + .stderr_contains("cp: cannot create hard link 'symlink' to 'foo'"); + assert!(at.symlink_exists(SYMLINK_NAME)); + assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME)); + assert!(at.file_exists(FILE_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + } + + #[test] + fn test_same_file_from_file_to_symlink_with_options_link_and_force() { + for option in ["-fl", "-dfl"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + scene + .ucmd() + .args(&[option, FILE_NAME, SYMLINK_NAME]) + .succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert!(at.file_exists(SYMLINK_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + } + + #[test] + fn test_same_file_from_file_to_symlink_with_options_backup_and_link() { + for option in ["-bl", "-bdl", "-bfl", "-bdfl"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let backup = "symlink~"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + scene + .ucmd() + .args(&[option, FILE_NAME, SYMLINK_NAME]) + .succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert!(at.file_exists(SYMLINK_NAME)); + assert!(at.symlink_exists(backup)); + assert_eq!(FILE_NAME, at.resolve_link(backup)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + } + + #[test] + fn test_same_file_from_file_to_symlink_with_options_symlink() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + scene + .ucmd() + .args(&["-s", FILE_NAME, SYMLINK_NAME]) + .fails() + .stderr_contains("cp: cannot create symlink 'symlink' to 'foo'"); + assert!(at.file_exists(FILE_NAME)); + assert!(at.symlink_exists(SYMLINK_NAME)); + assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + + #[test] + fn test_same_file_from_file_to_symlink_with_options_symlink_and_force() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + scene + .ucmd() + .args(&["-sf", FILE_NAME, SYMLINK_NAME]) + .succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert!(at.symlink_exists(SYMLINK_NAME)); + assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + // the following tests tries to copy a symlink to the file that symlink points to with + // various options + #[test] + fn test_same_file_from_symlink_to_file() { + for option in ["-d", "-f", "-df", "--rem"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + scene + .ucmd() + .args(&[option, SYMLINK_NAME, FILE_NAME]) + .fails() + .stderr_contains("'symlink' and 'foo' are the same file"); + assert!(at.file_exists(FILE_NAME)); + assert!(at.symlink_exists(SYMLINK_NAME)); + assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + } + + #[test] + fn test_same_file_from_symlink_to_file_with_option_backup() { + for option in ["-b", "-bf"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + scene + .ucmd() + .args(&[option, SYMLINK_NAME, FILE_NAME]) + .fails() + .stderr_contains("'symlink' and 'foo' are the same file"); + assert!(at.file_exists(FILE_NAME)); + assert!(at.symlink_exists(SYMLINK_NAME)); + assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + } + #[test] + fn test_same_file_from_symlink_to_file_with_option_backup_without_deref() { + for option in ["-bd", "-bdf"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let backup = "foo~"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + scene + .ucmd() + .args(&[option, SYMLINK_NAME, FILE_NAME]) + .succeeds(); + assert!(at.file_exists(backup)); + assert!(at.symlink_exists(FILE_NAME)); + // this doesn't makes sense but this is how gnu does it + assert_eq!(FILE_NAME, at.resolve_link(FILE_NAME)); + assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME)); + assert_eq!(at.read(backup), CONTENTS,); + } + } + + #[test] + fn test_same_file_from_symlink_to_file_with_options_link() { + for option in ["-l", "-dl", "-fl", "-bl", "-bfl"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + scene + .ucmd() + .args(&[option, SYMLINK_NAME, FILE_NAME]) + .succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert!(at.symlink_exists(SYMLINK_NAME)); + assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + } + + #[test] + fn test_same_file_from_symlink_to_file_with_option_symlink() { + for option in ["-s", "-sf"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + scene + .ucmd() + .args(&[option, SYMLINK_NAME, FILE_NAME]) + .fails() + .stderr_contains("'symlink' and 'foo' are the same file"); + assert!(at.file_exists(FILE_NAME)); + assert!(at.symlink_exists(SYMLINK_NAME)); + assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + } + + // the following tests tries to copy a file to the same file with various options + #[test] + fn test_same_file_from_file_to_file() { + for option in ["-d", "-f", "-df", "--rem"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write(FILE_NAME, CONTENTS); + scene + .ucmd() + .args(&[option, FILE_NAME, FILE_NAME]) + .fails() + .stderr_contains("'foo' and 'foo' are the same file"); + assert!(at.file_exists(FILE_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + } + #[test] + fn test_same_file_from_file_to_file_with_backup() { + for option in ["-b", "-bd"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write(FILE_NAME, CONTENTS); + scene + .ucmd() + .args(&[option, FILE_NAME, FILE_NAME]) + .fails() + .stderr_contains("'foo' and 'foo' are the same file"); + assert!(at.file_exists(FILE_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + } + + #[test] + fn test_same_file_from_file_to_file_with_options_backup_and_no_deref() { + for option in ["-bf", "-bdf"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let backup = "foo~"; + at.write(FILE_NAME, CONTENTS); + scene + .ucmd() + .args(&[option, FILE_NAME, FILE_NAME]) + .succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert!(at.file_exists(backup)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + assert_eq!(at.read(backup), CONTENTS,); + } + } + + #[test] + fn test_same_file_from_file_to_file_with_options_link() { + for option in ["-l", "-dl", "-fl", "-dfl", "-bl", "-bdl"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let backup = "foo~"; + at.write(FILE_NAME, CONTENTS); + scene + .ucmd() + .args(&[option, FILE_NAME, FILE_NAME]) + .succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert!(!at.file_exists(backup)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + } + + #[test] + fn test_same_file_from_file_to_file_with_options_link_and_backup_and_force() { + for option in ["-bfl", "-bdfl"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let backup = "foo~"; + at.write(FILE_NAME, CONTENTS); + scene + .ucmd() + .args(&[option, FILE_NAME, FILE_NAME]) + .succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert!(at.file_exists(backup)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + assert_eq!(at.read(backup), CONTENTS,); + } + } + + #[test] + fn test_same_file_from_file_to_file_with_options_symlink() { + for option in ["-s", "-sf"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write(FILE_NAME, CONTENTS); + scene + .ucmd() + .args(&[option, FILE_NAME, FILE_NAME]) + .fails() + .stderr_contains("'foo' and 'foo' are the same file"); + assert!(at.file_exists(FILE_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + } + + // the following tests tries to copy a symlink that points to a file to a symlink + // that points to the same file with various options + #[test] + fn test_same_file_from_symlink_to_symlink_with_option_no_deref() { + for option in ["-d", "-df"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let symlink1 = "sl1"; + let symlink2 = "sl2"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, symlink1); + at.symlink_file(FILE_NAME, symlink2); + scene.ucmd().args(&[option, symlink1, symlink2]).succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + assert_eq!(FILE_NAME, at.resolve_link(symlink1)); + assert_eq!(FILE_NAME, at.resolve_link(symlink2)); + } + } + + #[test] + fn test_same_file_from_symlink_to_symlink_with_option_force() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let symlink1 = "sl1"; + let symlink2 = "sl2"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, symlink1); + at.symlink_file(FILE_NAME, symlink2); + scene + .ucmd() + .args(&["-f", symlink1, symlink2]) + .fails() + .stderr_contains("'sl1' and 'sl2' are the same file"); + assert!(at.file_exists(FILE_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + assert_eq!(FILE_NAME, at.resolve_link(symlink1)); + assert_eq!(FILE_NAME, at.resolve_link(symlink2)); + } + + #[test] + fn test_same_file_from_symlink_to_symlink_with_option_rem() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let symlink1 = "sl1"; + let symlink2 = "sl2"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, symlink1); + at.symlink_file(FILE_NAME, symlink2); + scene.ucmd().args(&["--rem", symlink1, symlink2]).succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + assert_eq!(FILE_NAME, at.resolve_link(symlink1)); + assert!(at.file_exists(symlink2)); + assert_eq!(at.read(symlink2), CONTENTS,); + } + + #[test] + fn test_same_file_from_symlink_to_symlink_with_option_backup() { + for option in ["-b", "-bf"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let symlink1 = "sl1"; + let symlink2 = "sl2"; + let backup = "sl2~"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, symlink1); + at.symlink_file(FILE_NAME, symlink2); + scene.ucmd().args(&[option, symlink1, symlink2]).succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + assert_eq!(FILE_NAME, at.resolve_link(symlink1)); + assert!(at.file_exists(symlink2)); + assert_eq!(at.read(symlink2), CONTENTS,); + assert_eq!(FILE_NAME, at.resolve_link(backup)); + } + } + + #[test] + fn test_same_file_from_symlink_to_symlink_with_option_backup_and_no_deref() { + for option in ["-bd", "-bdf"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let symlink1 = "sl1"; + let symlink2 = "sl2"; + let backup = "sl2~"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, symlink1); + at.symlink_file(FILE_NAME, symlink2); + scene.ucmd().args(&[option, symlink1, symlink2]).succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + assert_eq!(FILE_NAME, at.resolve_link(symlink1)); + assert_eq!(FILE_NAME, at.resolve_link(symlink2)); + assert_eq!(FILE_NAME, at.resolve_link(backup)); + } + } + #[test] + fn test_same_file_from_symlink_to_symlink_with_option_link() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let symlink1 = "sl1"; + let symlink2 = "sl2"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, symlink1); + at.symlink_file(FILE_NAME, symlink2); + scene + .ucmd() + .args(&["-l", symlink1, symlink2]) + .fails() + .stderr_contains("cannot create hard link 'sl2' to 'sl1'"); + assert!(at.file_exists(FILE_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + assert_eq!(FILE_NAME, at.resolve_link(symlink1)); + assert_eq!(FILE_NAME, at.resolve_link(symlink2)); + } + + #[test] + fn test_same_file_from_symlink_to_symlink_with_option_force_link() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let symlink1 = "sl1"; + let symlink2 = "sl2"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, symlink1); + at.symlink_file(FILE_NAME, symlink2); + scene.ucmd().args(&["-fl", symlink1, symlink2]).succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + assert_eq!(FILE_NAME, at.resolve_link(symlink1)); + assert!(at.file_exists(symlink2)); + assert_eq!(at.read(symlink2), CONTENTS,); + } + + #[test] + fn test_same_file_from_symlink_to_symlink_with_option_backup_and_link() { + for option in ["-bl", "-bfl"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let symlink1 = "sl1"; + let symlink2 = "sl2"; + let backup = "sl2~"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, symlink1); + at.symlink_file(FILE_NAME, symlink2); + scene.ucmd().args(&[option, symlink1, symlink2]).succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + assert_eq!(FILE_NAME, at.resolve_link(symlink1)); + assert!(at.file_exists(symlink2)); + assert_eq!(at.read(symlink2), CONTENTS,); + assert_eq!(FILE_NAME, at.resolve_link(backup)); + } + } + + #[test] + fn test_same_file_from_symlink_to_symlink_with_option_symlink() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let symlink1 = "sl1"; + let symlink2 = "sl2"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, symlink1); + at.symlink_file(FILE_NAME, symlink2); + scene + .ucmd() + .args(&["-s", symlink1, symlink2]) + .fails() + .stderr_contains("cannot create symlink 'sl2' to 'sl1'"); + assert!(at.file_exists(FILE_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + assert_eq!(FILE_NAME, at.resolve_link(symlink1)); + assert_eq!(FILE_NAME, at.resolve_link(symlink2)); + } + + #[test] + fn test_same_file_from_symlink_to_symlink_with_option_symlink_and_force() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let symlink1 = "sl1"; + let symlink2 = "sl2"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, symlink1); + at.symlink_file(FILE_NAME, symlink2); + scene.ucmd().args(&["-sf", symlink1, symlink2]).succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + assert_eq!(FILE_NAME, at.resolve_link(symlink1)); + assert_eq!(symlink1, at.resolve_link(symlink2)); + } + + // the following tests tries to copy file to a hardlink of the same file with + // various options + #[test] + fn test_same_file_from_file_to_hardlink() { + for option in ["-d", "-f", "-df"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let hardlink = "hardlink"; + at.write(FILE_NAME, CONTENTS); + at.hard_link(FILE_NAME, hardlink); + + scene + .ucmd() + .args(&[option, FILE_NAME, hardlink]) + .fails() + .stderr_contains("'foo' and 'hardlink' are the same file"); + assert!(at.file_exists(FILE_NAME)); + assert!(at.file_exists(hardlink)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + } + + #[test] + fn test_same_file_from_file_to_hardlink_with_option_rem() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let hardlink = "hardlink"; + at.write(FILE_NAME, CONTENTS); + at.hard_link(FILE_NAME, hardlink); + scene + .ucmd() + .args(&["--rem", FILE_NAME, hardlink]) + .succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert!(at.file_exists(hardlink)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + + #[test] + fn test_same_file_from_file_to_hardlink_with_option_backup() { + for option in ["-b", "-bd", "-bf", "-bdf"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let hardlink = "hardlink"; + let backup = "hardlink~"; + at.write(FILE_NAME, CONTENTS); + at.hard_link(FILE_NAME, hardlink); + scene.ucmd().args(&[option, FILE_NAME, hardlink]).succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert!(at.file_exists(hardlink)); + assert!(at.file_exists(backup)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + } + + #[test] + fn test_same_file_from_file_to_hardlink_with_option_link() { + for option in ["-l", "-dl", "-fl", "-dfl", "-bl", "-bdl", "-bfl", "-bdfl"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let hardlink = "hardlink"; + at.write(FILE_NAME, CONTENTS); + at.hard_link(FILE_NAME, hardlink); + scene.ucmd().args(&[option, FILE_NAME, hardlink]).succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert!(at.file_exists(hardlink)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + } + + #[test] + fn test_same_file_from_file_to_hardlink_with_option_symlink() { + for option in ["-s", "-sf"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let hardlink = "hardlink"; + at.write(FILE_NAME, CONTENTS); + at.hard_link(FILE_NAME, hardlink); + scene + .ucmd() + .args(&[option, FILE_NAME, hardlink]) + .fails() + .stderr_contains("'foo' and 'hardlink' are the same file"); + assert!(at.file_exists(FILE_NAME)); + assert!(at.file_exists(hardlink)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + } + + // the following tests tries to copy symlink to a hardlink of the same symlink with + // various options + #[test] + fn test_same_file_from_hard_link_of_symlink_to_symlink() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let hardlink_to_symlink = "hlsl"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + at.hard_link(SYMLINK_NAME, hardlink_to_symlink); + scene + .ucmd() + .args(&[hardlink_to_symlink, SYMLINK_NAME]) + .fails() + .stderr_contains("cp: 'hlsl' and 'symlink' are the same file"); + assert!(at.file_exists(FILE_NAME)); + assert!(at.symlink_exists(SYMLINK_NAME)); + assert!(at.symlink_exists(hardlink_to_symlink)); + assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME)); + assert_eq!(FILE_NAME, at.resolve_link(hardlink_to_symlink)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + + #[test] + fn test_same_file_from_hard_link_of_symlink_to_symlink_with_option_force() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let hardlink_to_symlink = "hlsl"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + at.hard_link(SYMLINK_NAME, hardlink_to_symlink); + scene + .ucmd() + .args(&["-f", hardlink_to_symlink, SYMLINK_NAME]) + .fails() + .stderr_contains("cp: 'hlsl' and 'symlink' are the same file"); + assert!(at.file_exists(FILE_NAME)); + assert!(at.symlink_exists(SYMLINK_NAME)); + assert!(at.symlink_exists(hardlink_to_symlink)); + assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME)); + assert_eq!(FILE_NAME, at.resolve_link(hardlink_to_symlink)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + + #[test] + fn test_same_file_from_hard_link_of_symlink_to_symlink_with_option_no_deref() { + for option in ["-d", "-df"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let hardlink_to_symlink = "hlsl"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + at.hard_link(SYMLINK_NAME, hardlink_to_symlink); + scene + .ucmd() + .args(&[option, hardlink_to_symlink, SYMLINK_NAME]) + .succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert!(at.symlink_exists(SYMLINK_NAME)); + assert!(at.symlink_exists(hardlink_to_symlink)); + assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME)); + assert_eq!(FILE_NAME, at.resolve_link(hardlink_to_symlink)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + } + + #[test] + fn test_same_file_from_hard_link_of_symlink_to_symlink_with_option_rem() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let hardlink_to_symlink = "hlsl"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + at.hard_link(SYMLINK_NAME, hardlink_to_symlink); + scene + .ucmd() + .args(&["--rem", hardlink_to_symlink, SYMLINK_NAME]) + .succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert!(at.file_exists(SYMLINK_NAME)); + assert!(!at.symlink_exists(SYMLINK_NAME)); + assert!(at.symlink_exists(hardlink_to_symlink)); + assert_eq!(FILE_NAME, at.resolve_link(hardlink_to_symlink)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + assert_eq!(at.read(SYMLINK_NAME), CONTENTS,); + } + + #[test] + fn test_same_file_from_hard_link_of_symlink_to_symlink_with_option_backup() { + for option in ["-b", "-bf"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let backup = "symlink~"; + let hardlink_to_symlink = "hlsl"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + at.hard_link(SYMLINK_NAME, hardlink_to_symlink); + scene + .ucmd() + .args(&[option, hardlink_to_symlink, SYMLINK_NAME]) + .succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert!(at.file_exists(SYMLINK_NAME)); + assert!(!at.symlink_exists(SYMLINK_NAME)); + assert!(at.symlink_exists(hardlink_to_symlink)); + assert_eq!(FILE_NAME, at.resolve_link(hardlink_to_symlink)); + assert!(at.symlink_exists(backup)); + assert_eq!(FILE_NAME, at.resolve_link(backup)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + assert_eq!(at.read(SYMLINK_NAME), CONTENTS,); + } + } + + #[test] + fn test_same_file_from_hard_link_of_symlink_to_symlink_with_option_backup_and_no_deref() { + for option in ["-bd", "-bdf"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let backup = "symlink~"; + let hardlink_to_symlink = "hlsl"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + at.hard_link(SYMLINK_NAME, hardlink_to_symlink); + scene + .ucmd() + .args(&[option, hardlink_to_symlink, SYMLINK_NAME]) + .succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert!(at.symlink_exists(SYMLINK_NAME)); + assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME)); + assert!(at.symlink_exists(hardlink_to_symlink)); + assert_eq!(FILE_NAME, at.resolve_link(hardlink_to_symlink)); + assert!(at.symlink_exists(backup)); + assert_eq!(FILE_NAME, at.resolve_link(backup)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + } + + #[test] + fn test_same_file_from_hard_link_of_symlink_to_symlink_with_option_link() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let hardlink_to_symlink = "hlsl"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + at.hard_link(SYMLINK_NAME, hardlink_to_symlink); + scene + .ucmd() + .args(&["-l", hardlink_to_symlink, SYMLINK_NAME]) + .fails() + .stderr_contains("cannot create hard link 'symlink' to 'hlsl'"); + assert!(at.file_exists(FILE_NAME)); + assert!(at.symlink_exists(SYMLINK_NAME)); + assert!(at.symlink_exists(hardlink_to_symlink)); + assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME)); + assert_eq!(FILE_NAME, at.resolve_link(hardlink_to_symlink)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + + #[test] + fn test_same_file_from_hard_link_of_symlink_to_symlink_with_option_link_and_no_deref() { + for option in ["-dl", "-dfl"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let hardlink_to_symlink = "hlsl"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + at.hard_link(SYMLINK_NAME, hardlink_to_symlink); + scene + .ucmd() + .args(&[option, hardlink_to_symlink, SYMLINK_NAME]) + .succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert!(at.symlink_exists(SYMLINK_NAME)); + assert!(at.symlink_exists(hardlink_to_symlink)); + assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME)); + assert_eq!(FILE_NAME, at.resolve_link(hardlink_to_symlink)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + } + + #[test] + fn test_same_file_from_hard_link_of_symlink_to_symlink_with_option_link_and_force() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let hardlink_to_symlink = "hlsl"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + at.hard_link(SYMLINK_NAME, hardlink_to_symlink); + scene + .ucmd() + .args(&["-fl", hardlink_to_symlink, SYMLINK_NAME]) + .succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert!(at.file_exists(SYMLINK_NAME)); + assert!(!at.symlink_exists(SYMLINK_NAME)); + assert!(at.symlink_exists(hardlink_to_symlink)); + assert_eq!(FILE_NAME, at.resolve_link(hardlink_to_symlink)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + + #[test] + fn test_same_file_from_hard_link_of_symlink_to_symlink_with_option_link_and_backup() { + for option in ["-bl", "-bfl"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let backup = "symlink~"; + let hardlink_to_symlink = "hlsl"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + at.hard_link(SYMLINK_NAME, hardlink_to_symlink); + scene + .ucmd() + .args(&[option, hardlink_to_symlink, SYMLINK_NAME]) + .succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert!(at.file_exists(SYMLINK_NAME)); + assert!(!at.symlink_exists(SYMLINK_NAME)); + assert!(at.symlink_exists(hardlink_to_symlink)); + assert_eq!(FILE_NAME, at.resolve_link(hardlink_to_symlink)); + assert!(at.symlink_exists(backup)); + assert_eq!(FILE_NAME, at.resolve_link(backup)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + } + + #[test] + fn test_same_file_from_hard_link_of_symlink_to_symlink_with_options_backup_link_no_deref() { + for option in ["-bdl", "-bdfl"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let hardlink_to_symlink = "hlsl"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + at.hard_link(SYMLINK_NAME, hardlink_to_symlink); + scene + .ucmd() + .args(&[option, hardlink_to_symlink, SYMLINK_NAME]) + .succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert!(at.symlink_exists(SYMLINK_NAME)); + assert!(at.symlink_exists(hardlink_to_symlink)); + assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME)); + assert_eq!(FILE_NAME, at.resolve_link(hardlink_to_symlink)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + } + + #[test] + fn test_same_file_from_hard_link_of_symlink_to_symlink_with_option_symlink() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let hardlink_to_symlink = "hlsl"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + at.hard_link(SYMLINK_NAME, hardlink_to_symlink); + scene + .ucmd() + .args(&["-s", hardlink_to_symlink, SYMLINK_NAME]) + .fails() + .stderr_contains("cannot create symlink 'symlink' to 'hlsl'"); + assert!(at.file_exists(FILE_NAME)); + assert!(at.symlink_exists(SYMLINK_NAME)); + assert!(at.symlink_exists(hardlink_to_symlink)); + assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME)); + assert_eq!(FILE_NAME, at.resolve_link(hardlink_to_symlink)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + + #[test] + fn test_same_file_from_hard_link_of_symlink_to_symlink_with_option_symlink_and_force() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let hardlink_to_symlink = "hlsl"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + at.hard_link(SYMLINK_NAME, hardlink_to_symlink); + scene + .ucmd() + .args(&["-sf", hardlink_to_symlink, SYMLINK_NAME]) + .succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert!(at.symlink_exists(SYMLINK_NAME)); + assert!(at.symlink_exists(hardlink_to_symlink)); + assert_eq!(hardlink_to_symlink, at.resolve_link(SYMLINK_NAME)); + assert_eq!(FILE_NAME, at.resolve_link(hardlink_to_symlink)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } +}