Skip to content

Commit

Permalink
ln: allow final destination directory when using -nf (#5975)
Browse files Browse the repository at this point in the history
Closes: #5974
  • Loading branch information
jansheikkinen committed Sep 17, 2024
1 parent 39bfa40 commit 564dd47
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 36 deletions.
72 changes: 36 additions & 36 deletions src/uu/ln/src/ln.rs
Original file line number Diff line number Diff line change
Expand Up @@ -301,45 +301,45 @@ fn link_files_in_dir(files: &[PathBuf], target_dir: &Path, settings: &Settings)

let mut all_successful = true;
for srcpath in files {
let targetpath =
if settings.no_dereference && matches!(settings.overwrite, OverwriteMode::Force) {
// In that case, we don't want to do link resolution
// We need to clean the target
if target_dir.is_symlink() {
if target_dir.is_file() {
if let Err(e) = fs::remove_file(target_dir) {
show_error!("Could not update {}: {}", target_dir.quote(), e);
};
}
if target_dir.is_dir() {
// Not sure why but on Windows, the symlink can be
// considered as a dir
// See test_ln::test_symlink_no_deref_dir
if let Err(e) = fs::remove_dir(target_dir) {
show_error!("Could not update {}: {}", target_dir.quote(), e);
};
let targetpath = if settings.no_dereference
&& matches!(settings.overwrite, OverwriteMode::Force)
&& target_dir.is_symlink()
{
// In that case, we don't want to do link resolution
// We need to clean the target
if target_dir.is_file() {
if let Err(e) = fs::remove_file(target_dir) {
show_error!("Could not update {}: {}", target_dir.quote(), e);
};
}
if target_dir.is_dir() {
// Not sure why but on Windows, the symlink can be
// considered as a dir
// See test_ln::test_symlink_no_deref_dir
if let Err(e) = fs::remove_dir(target_dir) {
show_error!("Could not update {}: {}", target_dir.quote(), e);
};
}
target_dir.to_path_buf()
} else {
match srcpath.as_os_str().to_str() {
Some(name) => {
match Path::new(name).file_name() {
Some(basename) => target_dir.join(basename),
// This can be None only for "." or "..". Trying
// to create a link with such name will fail with
// EEXIST, which agrees with the behavior of GNU
// coreutils.
None => target_dir.join(name),
}
}
target_dir.to_path_buf()
} else {
match srcpath.as_os_str().to_str() {
Some(name) => {
match Path::new(name).file_name() {
Some(basename) => target_dir.join(basename),
// This can be None only for "." or "..". Trying
// to create a link with such name will fail with
// EEXIST, which agrees with the behavior of GNU
// coreutils.
None => target_dir.join(name),
}
}
None => {
show_error!("cannot stat {}: No such file or directory", srcpath.quote());
all_successful = false;
continue;
}
None => {
show_error!("cannot stat {}: No such file or directory", srcpath.quote());
all_successful = false;
continue;
}
};
}
};

if linked_destinations.contains(&targetpath) {
// If the target file was already created in this ln call, do not overwrite
Expand Down
61 changes: 61 additions & 0 deletions tests/by-util/test_ln.rs
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,67 @@ fn test_symlink_no_deref_dir() {
assert_eq!(at.resolve_link(link), dir1);
}

#[test]
fn test_symlink_no_deref_file_in_destination_dir() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;

let file1 = "foo";
let file2 = "bar";

let dest = "baz";

let link1 = "baz/foo";
let link2 = "baz/bar";

at.touch(file1);
at.touch(file2);
at.mkdir(dest);

assert!(at.file_exists(file1));
assert!(at.file_exists(file2));
assert!(at.dir_exists(dest));

// -n and -f should work alone
scene
.ucmd()
.args(&["-sn", file1, dest])
.succeeds()
.no_stderr();
assert!(at.is_symlink(link1));
assert_eq!(at.resolve_link(link1), file1);

scene
.ucmd()
.args(&["-sf", file1, dest])
.succeeds()
.no_stderr();
assert!(at.is_symlink(link1));
assert_eq!(at.resolve_link(link1), file1);

// -n alone should fail if destination exists already (it should now)
scene.ucmd().args(&["-sn", file1, dest]).fails();

// -nf should also work
scene
.ucmd()
.args(&["-snf", file1, dest])
.succeeds()
.no_stderr();
assert!(at.is_symlink(link1));
assert_eq!(at.resolve_link(link1), file1);

scene
.ucmd()
.args(&["-snf", file1, file2, dest])
.succeeds()
.no_stderr();
assert!(at.is_symlink(link1));
assert_eq!(at.resolve_link(link1), file1);
assert!(at.is_symlink(link2));
assert_eq!(at.resolve_link(link2), file2);
}

#[test]
fn test_symlink_no_deref_file() {
let scene = TestScenario::new(util_name!());
Expand Down

0 comments on commit 564dd47

Please sign in to comment.